从二进制形式转换浮点 NaN 值,反之亦然导致不匹配

Posted

技术标签:

【中文标题】从二进制形式转换浮点 NaN 值,反之亦然导致不匹配【英文标题】:Converting float NaN values from binary form and vice-versa results a mismatch 【发布时间】:2016-04-26 16:35:34 【问题描述】:

我在没有任何算术的情况下进行了“字节[4] -> 浮点数 -> 字节[4]”的转换。 以字节为单位,我有一个 IEEE-754 格式的单精度数字(每个数字 4 个字节,机器中的小端序)。 我遇到了一个问题,当字节表示一个未逐字转换的 NaN 值时。 例如:

0x1B, 0xC4, 0xAB, 0x7F -> NaN -> 0x1B, 0xC4, 0xEB, 0x7F

复制代码:

using System;
using System.Linq;

namespace StrangeFloat

    class Program
    
        private static void PrintBytes(byte[] array)
        
            foreach (byte b in array)
            
                Console.Write("0:X2", b);
            
            Console.WriteLine();
        

        static void Main(string[] args)
        
            byte[] strangeFloat =  0x1B, 0xC4, 0xAB, 0x7F ;
            float[] array = new float[1];
            Buffer.BlockCopy(strangeFloat, 0, array, 0, 4);
            byte[] bitConverterResult = BitConverter.GetBytes(array[0]);

            PrintBytes(strangeFloat);
            PrintBytes(bitConverterResult);
            bool isEqual = strangeFloat.SequenceEqual(bitConverterResult);
            Console.WriteLine("IsEqual: 0", isEqual);
        
    

结果(https://ideone.com/p5fsrE):

1BC4AB7F
1BC4EB7F
IsEqual: False

此行为取决于平台和配置:此代码在 x64 上的所有配置或 x86/Debug 中转换数字而不会出错。在 x86/Release 上存在错误。

另外,如果我改变了

byte[] bitConverterResult = BitConverter.GetBytes(array[0]);

float f = array[0];
byte[] bitConverterResult = BitConverter.GetBytes(f);

那么它在 x86/Debug 上也会出错。

我确实研究了这个问题,发现编译器生成的 x86 代码使用 FPU 寄存器 (!) 来保存浮点值(FLD/FST 指令)。但是 FPU 将尾数的高位设置为 1 而不是 0,因此它会修改值,尽管逻辑只是传递一个值而不进行更改。 在 x64 平台上,使用了 xmm0 寄存器 (SSE),它工作正常。

[问题]

这是什么:它是某处记录的NaN 值的未定义行为 还是JIT/优化错误

为什么编译器在不进行算术运算时使用 FPU 和 SSE?

更新 1

调试配置 - 通过堆栈传递值而没有副作用 - 正确结果

    byte[] bitConverterResult = BitConverter.GetBytes(array[0]);
02232E45  mov         eax,dword ptr [ebp-44h]  
02232E48  cmp         dword ptr [eax+4],0  
02232E4C  ja          02232E53  
02232E4E  call        71EAC65A  
02232E53  push        dword ptr [eax+8]   // eax+8 points to "1b c4 ab 7f" CORRECT!
02232E56  call        7136D8E4  
02232E5B  mov         dword ptr [ebp-5Ch],eax // eax points to managed
// array data "fc 35 d7 70 04 00 00 00 __1b c4 ab 7f__" and this is correct
02232E5E  mov         eax,dword ptr [ebp-5Ch]  
02232E61  mov         dword ptr [ebp-48h],eax 

发布配置 - 优化器或 JIT 通过 FPU 寄存器进行奇怪的传递并破坏数据 - 不正确

    byte[] bitConverterResult = BitConverter.GetBytes(array[0]);
00B12DE8  cmp         dword ptr [edi+4],0  
00B12DEC  jbe         00B12E3B  
00B12DEE  fld         dword ptr [edi+8]     // edi+8 points to "1b c4 ab 7f"
00B12DF1  fstp        dword ptr [ebp-10h]   // ebp-10h points to "1b c4 eb 7f" (FAIL)
00B12DF4  mov         ecx,dword ptr [ebp-10h]  
00B12DF7  call        70C75810  
00B12DFC  mov         edi,eax  
00B12DFE  mov         ecx,esi  
00B12E00  call        dword ptr ds:[4A70860h] 

【问题讨论】:

IEEE 规范中有多个对NaN 有效的值。 调试和发布是否得到相同的结果?我相信调试是使用软件来模拟 FPU,而在计算机中发布使用 FPU。电脑几岁了?我相信某些 UP 浮点单元存在已知问题。 英特尔处理器手册:“如果源操作数中的一个或两个都是NaN,并且屏蔽了浮点无效操作异常,结果如表4-7所示。当转换一个SNaN时对于 QNaN,转换是通过将 SNaN 的最高有效小数位设置为 1 来处理的。此外,当源操作数之一是 SNaN 时,它设置的浮点无效操作异常标志. 请注意,对于源操作数的某些组合,x87 FPU 操作和 SSE/SSE2/SSE3/SSE4.1 操作的结果是不同的。英特尔 AVX 遵循与 SSE/SSE2 相同的行为..." @jdweng 我在调试和发布时得到不同的结果,请参阅更新后:在调试模式下,数据通过堆栈传递,这没关系,但在发布模式下,数据通过 FPU 泵送 - 为什么优化器/JIT正在这样做吗? (CPU Core2 Quad Q9550,这不是硬件问题) x86 抖动使用 FPU 处理浮点值。这不是错误。您认为这些字节值是采用 float 参数的方法的正确参数的假设是错误的。信号 NaN 的唯一用途是生成异常。 .NET 框架没有。 .NET 代码执行的任何浮点运算都无法生成这些字节值。您需要调查这些字节值的来源,它有一个错误。 【参考方案1】:

我只是将@HansPassant 评论翻译为答案。

"x86 jitter 使用 FPU 处理浮点值。这是 不是错误。您假设这些字节值是正确的 接受浮点参数的方法的参数是错误的。”

换句话说,这只是一个 GIGO 案例(Garbage In, Garbage Out)。

【讨论】:

以上是关于从二进制形式转换浮点 NaN 值,反之亦然导致不匹配的主要内容,如果未能解决你的问题,请参考以下文章

Dask 从二进制文件中读取数据

c ++函数在返回时将浮点值转换为NaN

测试浮点 NaN 会导致堆栈溢出

用python从二进制文件中读取32位带符号的ieee 754浮点?

在C中从二进制转换为char

Python从二进制字符串转换为十六进制