【问题标题】:Unpacking hex-encoded floats解包十六进制编码的浮点数
【发布时间】:2012-04-20 17:03:29
【问题描述】:

我正在尝试将以下 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

因此它读取 32 位的十六进制编码字符串,使用 unhexlify 方法将其转换为字节数组,并将其解释为 little-endian 浮点值。

以下几乎可以工作,但代码有点糟糕(最后一个 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)

(edit2:添加了另一个值,00000080 == -0.0

【问题讨论】:

    标签: python c++ floating-point hex


    【解决方案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
    • 另外,自己逐位执行的好处是您可以根据字节序从左到右或从右到左循环
    • @OrgnlDave -- 这就是tolower 存在的原因。是的,在字节序上,虽然它变得有点棘手(对于单个字节数字不要交换)
    【解决方案2】:

    以下是为删除#ifdef BIG_ENDIAN 块而修改的更新代码。它使用应该与主机字节顺序无关的读取技术。它通过将十六进制字节(源字符串中的小端)读取为与 iostream std::hex 运算符兼容的大端字符串格式来实现这一点。一旦采用这种格式,主机字节顺序是什么就无关紧要了。

    此外,它还修复了 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.9ish,但是这样读取为 4.6e-41 左右
    • ntohl 不适合这种用法:它将 big-endian 转换为 native-endian,而所需的转换是 little-endian 到 native-endian。
    • 此版本更正了第一个版本中的字符串顺序问题。您的所有三个测试值都在小端机器上正确显示。如果您有访问权限,请在大端系统上进行验证。
    • 哦,呵呵,#ifdef BIG_ENDIAN 是完全错误的并且误导了我(它总是被定义的,即使在这个 little-endian 机器上也是如此)。由于#else 从不运行,这与我的代码相似,但有更好的方式读取字符串(并正确终止char[]
    • 目标变量已签名:int32_t var。结果,iostream 库将0x80000000 转换为0x7fffffff。通过将其更改为 uint32_t var 它解决了这个问题。
    【解决方案3】:

    这就是我们最终的结果,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;
        }
    

    【讨论】:

      猜你喜欢
      • 2012-06-26
      • 1970-01-01
      • 1970-01-01
      • 2011-12-05
      • 1970-01-01
      • 2014-03-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多