【问题标题】:Converting float values from big endian to little endian将浮点值从大端转换为小端
【发布时间】:2011-02-16 11:38:25
【问题描述】:

是否可以将floats 从大端转换为小端?我通过 TCP 将 PowerPC 平台的大端值发送到 Windows 进程(小端)。这个值是一个float,但是当我将memcpy 的值转换为Win32 浮点类型然后在那个值上调用_byteswap_ulong 时,我总是得到0.0000?

我做错了什么?

【问题讨论】:

标签: c++ endianness


【解决方案1】:

@morteza@AnotherParker 已经提到了 Boost 库,指出已删除对 float 的支持。但是,自从他们编写了 cmets 后,它就被添加回了库的一个子集中。

使用Boost.Endian conversion functions,版本 1.77.0 在我写这个答案时,您可以执行以下操作:

float input = /* some value */;
float reversed = input;
boost::endian::endian_reverse_inplace(reversed);

查看FAQ 了解支持被删除然后部分添加回来的原因(主要是因为颠倒的float 可能不再有效)和here 的支持历史记录。

【讨论】:

  • 我仍然不明白存储在给定字节序中的 IEEE 754 float 在被具有相反字节序的系统交换其字节后如何变成 NaN。常见问题解答提到即使对于整数和 FP 具有相同字节序的系统也会发生这种情况。没有例子,我不相信他们的说法。
  • @cesss 它可以独立于系统整数字节序发生。事实上,它是 IEEE 754 定义所固有的。 NaN 被定义为指数用1s 和非零尾数填充的数字,您可以在字节反转后在实践中得到。例如,在我的amd64 系统(小端)上,四个字节7f ffffffa0 0 0 被解释为5.75752e-41,但反过来给我一个信号NaN。
  • 显然,如果您在主机系统中对具有正确字节序的float 进行字节交换,然后尝试在 same 主机上读取它,您会得到什么是垃圾。但这不是重点。关键是FAQ声称,如果您在与第一台主机具有相反字节序的主机上读取这样的字节交换float,您可以获得NaN。 AFAIK,这是不可能的,因为第二台主机读取float 的方式与具有本机字节序的第一台主机完全相同(当然,第二台主机读取它是字节交换的,但它也具有相反的字节序,所以它看起来一样)
  • @cesss 我认为您将声明归因于它没有提出的常见问题解答。例如,他们从不谈论具有不同字节序的两个系统之间的传输。我认为这里的主要问题是,在字节顺序转换过程中,您可能会使用 floats 获得无效的临时值,而整数则无法获得,并且用户可能因此遇到副作用。
  • 啊,我想这清楚了,谢谢!我从不将交换的float 存储为float 类型,所以我没有想到这一点。当他们提到代码可以生成 NaN 时,我认为他们的意思是在传输结束时,而不是在中间。谢谢!
【解决方案2】:

在某些情况下,尤其是在 modbus 上:浮点数的网络字节顺序是:

nfloat[0] = float[1]
nfloat[1] = float[0]
nfloat[2] = float[3]
nfloat[3] = float[2]

【讨论】:

    【解决方案3】:

    进行字节交换的一种优雅方式是使用联合:

    float big2little (float f)
    {
        union
        {
            float f;
            char b[4];
        } src, dst;
    
        src.f = f;
        dst.b[3] = src.b[0];
        dst.b[2] = src.b[1];
        dst.b[1] = src.b[2];
        dst.b[0] = src.b[3];
        return dst.f;
    }
    

    按照 jjmerelo 的建议编写循环,更通用的解决方案可能是:

    typedef float number_t;
    #define NUMBER_SIZE sizeof(number_t)
    
    number_t big2little (number_t n)
    {
        union
        {
            number_t n;
            char b[NUMBER_SIZE];
        } src, dst;
    
        src.n = n;
        for (size_t i=0; i<NUMBER_SIZE; i++)
            dst.b[i] = src.b[NUMBER_SIZE-1 - i];
    
        return dst.n;
    }
    

    【讨论】:

    • 不应该把赋值dst.b src.b放到一个循环里吗?
    • 确实,写一个循环是一种可能的解决方案。首先,我这样认为是为了使代码更容易扩展到其他大小(例如,转换双精度数)。编译器在优化时,通过生成与没有循环相同的代码来序列化循环。将四个赋值不带循环的原因是为了简单起见,因为也许它更容易理解。带有循环的解决方案是: for (unsigned i=0; i
    • 这是 C++ 中未定义的行为。读取未在联合中分配的变量是无效的。它可能在大多数编译器中运行良好 - 但不看就过马路也是如此。
    • 非常感谢您的评论。经过 30 年的 C 编程,我已经了解了联合的这一方面。在阅读您的评论时,我认为这将是 C++ 中引入的一个功能,但我搜索了 Kernighan-Ritchie 的第一版并且它已经在那里:如果它是您编写的最后一个工会成员,那么阅读工会成员是安全的,但其他情况下行为仍未定义。
    【解决方案4】:

    来自SDL_endian.h,略有改动:

    std::uint32_t Swap32(std::uint32_t x)
    {
        return static_cast<std::uint32_t>((x << 24) | ((x << 8) & 0x00FF0000) |
                                          ((x >> 8) & 0x0000FF00) | (x >> 24));
    }
    
    float SwapFloat(float x)
    {
        union
        {
            float f;
            std::uint32_t ui32;
        } swapper;
        swapper.f = x;
        swapper.ui32 = Swap32(swapper.ui32);
        return swapper.f;
    }
    

    【讨论】:

      【解决方案5】:

      不要将数据直接存储为浮点类型。将其保留为 char 数据,交换字节并然后将其视为浮点数。

      【讨论】:

        【解决方案6】:

        这是一个可以反转任何类型的字节顺序的函数。

        template <typename T>
        T bswap(T val) {
            T retVal;
            char *pVal = (char*) &val;
            char *pRetVal = (char*)&retVal;
            int size = sizeof(T);
            for(int i=0; i<size; i++) {
                pRetVal[size-1-i] = pVal[i];
            }
        
            return retVal;
        }
        

        【讨论】:

          【解决方案7】:

          只需反转四个字节即可

          float ReverseFloat( const float inFloat )
          {
             float retVal;
             char *floatToConvert = ( char* ) & inFloat;
             char *returnFloat = ( char* ) & retVal;
          
             // swap the bytes into a temporary buffer
             returnFloat[0] = floatToConvert[3];
             returnFloat[1] = floatToConvert[2];
             returnFloat[2] = floatToConvert[1];
             returnFloat[3] = floatToConvert[0];
          
             return retVal;
          }
          

          【讨论】:

          • 这是完全合法的 C 代码,任何编译器都不应该破坏它。我已经用 VC6、Visual Studio 2008、Visual Studio 2010 和 c++ Builder 2010 对其进行了测试。这些编译器都没有破坏这段代码。
          • @Tomek:这确实违反了严格的别名规则。 C 和 C++ 都明确允许将 any 类型的对象作为 char 数组访问(因此,通过char*)。您发布的链接中的“通过联合强制转换”黑客导致未定义的行为(从最后一个写入之外的联合成员中读取会导致未定义的行为)。
          • @Tomek:对你不好没关系;它的行为是否定义明确很重要。通过 char* 重新解释是明确定义的(参见 C++03 §3.10/15)。联合黑客不是(参见 C++03 §9.5/1)。如果您熟悉该语言,那么避免未定义或实现定义的行为并不是特别困难。
          • 这段代码还有一个小问题。尽管有函数的名称,但它的行为与 ntohl() 完全不同。如果字节序已经正确,它应该什么都不做。在这里,它无论如何都会交换字节。
          • @Tomek:所以你更喜欢调用 UB 的解决方案,而不是定义完美的解决方案?这……一点意义都没有。你说“代码是合法的,但并不意味着它有效。”,然后,当谈到使用联合时......“(AFAIR 它是可怕的 UB)但它似乎有效。”。我想我从未听过比这更前后矛盾的论点。你怎么能说“如果你想在如此低的水平上摆弄比特,你迟早会遇到 UB 或 IDB。”?你在说什么?这是完全不真实的,你只需要知道你在做什么。
          【解决方案8】:

          这个值是一个浮点数,但是当我将这个值“memcpy”成一个 win32 浮点类型然后在那个值上调用 _byteswap_ulong 时,我总是得到 0.0000?

          这应该可行。你能发布你的代码吗?

          但是,如果您关心性能(也许您不关心,在这种情况下您可以忽略其余部分),应该可以避免 memcpy,方法是直接将它加载到目标位置并在那里交换字节,或者使用在复制时进行交换的交换。

          【讨论】:

            【解决方案9】:

            我很久以前就发现了类似的东西。 很好,但吞食后果自负。我什至没有编译它:

            void * endian_swap(void * arg)
            {
                unsigned int n = *((int*)arg);
                n = ((n >>  8) & 0x00ff00ff) | ((n <<  8) & 0xff00ff00);
                n = ((n >> 16) & 0x0000ffff) | ((n << 16) & 0xffff0000);
                *arg = n;   
            
                return arg;
            }
            

            【讨论】:

            • 嘿嘿。知道那会被否决。只是为了搞笑而发布它。很多很多年前我在 EA 采访时,他们有一个版本可以在 1、2、4、8 和 16 位宽度上进行转换,并询问它做了什么。
            • 首先,我认为这很明显,这不过是一种娱乐。其次,我不确定别名规则是否适用。虽然 arg 有可能指向将由 n 拥有的内存,但优化器不会发现这一点,因为它在分配给 *arg 后再也不会使用 n。除非您执行以下操作,否则别名规则不会生效:a=5;*b=7;c=a+(*b);,其中 c 的值不能从 a 的缓存值计算,因为分配给*b 可能影响了它。虽然我应该说 *((int)arg) = n; =]
            • 好吧,如果性能很重要,那么这个版本会比字节洗牌更受欢迎。一旦您将值作为 dword 读回,字节混洗将表现出部分停顿。在这种情况下,您始终保持双字大小,并且可以很好地流水线化,没有停顿。我对 EA 展示它并不感到惊讶,因为游戏开发人员将此类函数用于时间关键代码。
            • 也就是说,我可能更喜欢一个更易读的版本,四个班次组合在一起。然而,有些平台的轮班性能会因轮班计数 (PPC) 较高而变得更差,因此他们拥有的平台可能会表现得更好。
            • *arg = n;?分配给取消引用的 void 指针是否可以在不强制转换的情况下工作?我希望编译器会抱怨。
            【解决方案10】:

            使用 ntoa 和相关功能从网络转换为主机以及从主机转换为网络可能会更容易。它的优点是可移植。 Here 是一篇文章的链接,该文章解释了如何执行此操作。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2018-10-19
              • 2013-12-30
              • 2013-10-17
              • 2019-12-03
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多