解码十六进制浮点数

6

我正在尝试将以下 Python 代码翻译成 C++:

import struct
import binascii


inputstring = ("0000003F" "0000803F" "AD10753F" "00000080")
num_vals = 4

for i in range(num_vals):
    rawhex = inputstring[i*8:(i*8)+8]

    # <f for little endian float
    val = struct.unpack("<f", binascii.unhexlify(rawhex))[0]
    print val

    # Output:
    # 0.5
    # 1.0
    # 0.957285702229
    # -0.0

这段代码读取了16进制编码字符串的32位,并使用unhexlify方法将其转换为字节数组,再将其解释为小端浮点值。

下面的代码几乎可以工作,但是代码有些糟糕(最后一个00000080解析不正确):

#include <sstream>
#include <iostream>


int main()
{
    // The hex-encoded string, and number of values are loaded from a file.
    // The num_vals might be wrong, so some basic error checking is needed.
    std::string inputstring = "0000003F" "0000803F" "AD10753F" "00000080";
    int num_vals = 4;


    std::istringstream ss(inputstring);

    for(unsigned int i = 0; i < num_vals; ++i)
    {
        char rawhex[8];

// The ifdef is wrong. It is not the way to detect endianness (it's
// always defined)
#ifdef BIG_ENDIAN
        rawhex[6] = ss.get();
        rawhex[7] = ss.get();

        rawhex[4] = ss.get();
        rawhex[5] = ss.get();

        rawhex[2] = ss.get();
        rawhex[3] = ss.get();

        rawhex[0] = ss.get();
        rawhex[1] = ss.get();
#else
        rawhex[0] = ss.get();
        rawhex[1] = ss.get();

        rawhex[2] = ss.get();
        rawhex[3] = ss.get();

        rawhex[4] = ss.get();
        rawhex[5] = ss.get();

        rawhex[6] = ss.get();
        rawhex[7] = ss.get();
#endif

        if(ss.good())
        {
            std::stringstream convert;
            convert << std::hex << rawhex;
            int32_t val;
            convert >> val;

            std::cerr << (*(float*)(&val)) << "\n";
        }
        else
        {
            std::ostringstream os;
            os << "Not enough values in LUT data. Found " << i;
            os << ". Expected " << num_vals;
            std::cerr << os.str() << std::endl;
            throw std::exception();
        }
    }
}

(在 OS X 10.7/gcc-4.2.1 上编译,只需使用简单的 g++ blah.cpp 命令)
特别地,我想摆脱 BIG_ENDIAN 宏定义,因为我确信有更好的方法来解决这个问题,如this post所讨论的那样。
其他一些随机细节 - 我不能使用 Boost(对于该项目而言过于庞大)。字符串通常包含 1536(83*3)至 98304 浮点值(323*3),最多不超过 786432(643*3)。
(更新2:添加了另一个值,00000080 == -0.0
3个回答

1
我认为整个istringstring这一套太过复杂了。逐位解析自己实现会更容易。
首先,创建一个将十六进制数字转换为整数的函数:
signed char htod(char c)
{
  c = tolower(c);
  if(isdigit(c))
    return c - '0';

  if(c >= 'a' && c <= 'f')
    return c - 'a' + 10;

  return -1;
}

然后将字符串简单地转换成整数。下面的代码不检查错误并假定大端序--但是你应该能够填写细节。

unsigned long t = 0;
for(int i = 0; i < s.length(); ++i)
  t |= (t << 4) & htod(s[i]);

那么你的浮点数是

float f = * (float *) &t;

我认为你的意思是 (c - 'A') + 10; 假设它只会是大写字母 A。 - std''OrgnlDave
此外,自己逐位进行数字处理的优点是可以根据大小端性质选择从左到右或从右到左循环。 - std''OrgnlDave
@OrgnlDave -- 这就是为什么要使用 tolower 的原因。对于字节序,是的,尽管它变得稍微棘手一些(对于单个字节,数字不会交换)。 - George Skoptsov

1
以下是您更新后的代码,已修改以删除#ifdef BIG_ENDIAN块。它使用一种读取技术,应该是主机字节顺序独立的。它通过将十六进制字节(在您的源字符串中是小端)读入与iostream std :: hex运算符兼容的大端字符串格式中来实现此操作。一旦处于此格式,主机字节顺序就不重要了。
另外,它修复了一个bug,即rawhex需要以零结尾才能插入convert,否则可能存在一些尾随垃圾数据。
我没有大端系统进行测试,请在您的平台上进行验证。这是在Cygwin下编译和测试的。
#include <sstream>
#include <iostream>

int main()
{
    // The hex-encoded string, and number of values are loaded from a file.
    // The num_vals might be wrong, so some basic error checking is needed.
    std::string inputstring = "0000003F0000803FAD10753F00000080";
    int num_vals = 4;
    std::istringstream ss(inputstring);
    size_t const k_DataSize = sizeof(float);
    size_t const k_HexOctetLen = 2;

    for (uint32_t i = 0; i < num_vals; ++i)
    {
        char rawhex[k_DataSize * k_HexOctetLen + 1];

        // read little endian string into memory array
        for (uint32_t j=k_DataSize; (j > 0) && ss.good(); --j)
        {
            ss.read(rawhex + ((j-1) * k_HexOctetLen), k_HexOctetLen);
        }

        // terminate the string (needed for safe conversion)
        rawhex[k_DataSize * k_HexOctetLen] = 0;

        if (ss.good())
        {
            std::stringstream convert;
            convert << std::hex << rawhex;
            uint32_t val;
            convert >> val;

            std::cerr << (*(float*)(&val)) << "\n";
        }
        else
        {
            std::ostringstream os;
            os << "Not enough values in LUT data. Found " << i;
            os << ". Expected " << num_vals;
            std::cerr << os.str() << std::endl;
            throw std::exception();
        }
    }
}

这看起来好多了,但与原始代码相比,一些值被错误地读取。我已经使用 AD10753F 更新了示例字符串,它应该大约是 0.9 左右,但是在这种情况下被读取为 4.6e-41 或者类似的值。 - dbr
1
ntohl 在这种情况下是错误的:它将大端序转换为本机序,而所需的转换是小端序到本机序。 - ephemient
此版本修正了第一个版本中的字符串顺序问题。在小端机器上,您的所有三个测试值都正确显示。如果您可以访问大端系统,请验证一下。 - Amardeep AC9MF
哦,嘿,#ifdef BIG_ENDIAN 的东西完全是错的,让我误导了(即使在这个小端机器上也总是定义)。由于 #else 从不运行,所以这类似于我所拥有的代码,但更好地读取字符串的方法(并正确终止 char[])。 - dbr
1
目标变量已被声明为有符号数:int32_t var。由于iostream库将0x80000000转换为0x7fffffff,因此出现了问题。将其更改为uint32_t var可以解决这个问题。 - Amardeep AC9MF
显示剩余2条评论

-1

这就是我们最终得到的结果,OpenColorIO/src/core/FileFormatIridasLook.cpp

(Amardeep的答案,使用无符号的uint32_t修复也可能有效)

    // convert hex ascii to int
    // return true on success, false on failure
    bool hexasciitoint(char& ival, char character)
    {
        if(character>=48 && character<=57) // [0-9]
        {
            ival = static_cast<char>(character-48);
            return true;
        }
        else if(character>=65 && character<=70) // [A-F]
        {
            ival = static_cast<char>(10+character-65);
            return true;
        }
        else if(character>=97 && character<=102) // [a-f]
        {
            ival = static_cast<char>(10+character-97);
            return true;
        }

        ival = 0;
        return false;
    }

    // convert array of 8 hex ascii to f32
    // The input hexascii is required to be a little-endian representation
    // as used in the iridas file format
    // "AD10753F" -> 0.9572857022285461f on ALL architectures

    bool hexasciitofloat(float& fval, const char * ascii)
    {
        // Convert all ASCII numbers to their numerical representations
        char asciinums[8];
        for(unsigned int i=0; i<8; ++i)
        {
            if(!hexasciitoint(asciinums[i], ascii[i]))
            {
                return false;
            }
        }

        unsigned char * fvalbytes = reinterpret_cast<unsigned char *>(&fval);

#if OCIO_LITTLE_ENDIAN
        // Since incoming values are little endian, and we're on little endian
        // preserve the byte order
        fvalbytes[0] = (unsigned char) (asciinums[1] | (asciinums[0] << 4));
        fvalbytes[1] = (unsigned char) (asciinums[3] | (asciinums[2] << 4));
        fvalbytes[2] = (unsigned char) (asciinums[5] | (asciinums[4] << 4));
        fvalbytes[3] = (unsigned char) (asciinums[7] | (asciinums[6] << 4));
#else
        // Since incoming values are little endian, and we're on big endian
        // flip the byte order
        fvalbytes[3] = (unsigned char) (asciinums[1] | (asciinums[0] << 4));
        fvalbytes[2] = (unsigned char) (asciinums[3] | (asciinums[2] << 4));
        fvalbytes[1] = (unsigned char) (asciinums[5] | (asciinums[4] << 4));
        fvalbytes[0] = (unsigned char) (asciinums[7] | (asciinums[6] << 4));
#endif
        return true;
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接