从二进制形式转换 float NaN 值,反之亦然会导致不匹配
Converting float NaN values from binary form and vice-versa results a mismatch
我在没有任何算术的情况下进行了转换 "bytes[4] -> float number -> bytes[4]"。
以字节为单位,我有一个 IEEE-754 格式的单精度数字(每个数字 4 个字节,机器中的小端顺序)。
我遇到了一个问题,当 bytes 代表一个 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
此行为取决于平台和配置:此代码在所有配置或 x86/Debug 中在 x64 上无错误地转换数字。在 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/optimization 错误 的未定义行为?
为什么编译器在没有进行算术运算时使用 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]
我只是翻译@HansPassant 评论作为答案。
"The x86 jitter uses the FPU to handle floating point values. This is
not a bug. Your assumption that those byte values are a proper
argument to a method that takes a float argument is just wrong."
换句话说,这只是一个 GIGO 案例(垃圾输入,垃圾输出)。
我在没有任何算术的情况下进行了转换 "bytes[4] -> float number -> bytes[4]"。 以字节为单位,我有一个 IEEE-754 格式的单精度数字(每个数字 4 个字节,机器中的小端顺序)。 我遇到了一个问题,当 bytes 代表一个 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
此行为取决于平台和配置:此代码在所有配置或 x86/Debug 中在 x64 上无错误地转换数字。在 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/optimization 错误 的未定义行为?
为什么编译器在没有进行算术运算时使用 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]
我只是翻译@HansPassant 评论作为答案。
"The x86 jitter uses the FPU to handle floating point values. This is not a bug. Your assumption that those byte values are a proper argument to a method that takes a float argument is just wrong."
换句话说,这只是一个 GIGO 案例(垃圾输入,垃圾输出)。