I make a conversion "bytes[4] -> float number -> bytes[4]" without any arithmetics. In bytes I have a single precision number in IEEE-754 format (4 bytes per number, little endian order as in a machine). I encounter a issue, when bytes represents a NaN value converted not verbatim. For example:
{ 0x1B, 0xC4, 0xAB, 0x7F } -> NaN -> { 0x1B, 0xC4, 0xEB, 0x7F }
Code for reproduction:
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);
}
}
}
Result ( https://ideone.com/p5fsrE ):
1BC4AB7F
1BC4EB7F
IsEqual: False
This behaviour depends from platform and configuration: this code convert a number without errors on x64 in all configurations or in x86/Debug. On x86/Release an error exists.
Also, if I change
byte[] bitConverterResult = BitConverter.GetBytes(array[0]);
to
float f = array[0];
byte[] bitConverterResult = BitConverter.GetBytes(f);
then it erroneus also on x86/Debug.
I do research the problem and found that compiler generate x86 code that use a FPU registers (!) to a hold a float value (FLD/FST instructions). But FPU set a high bit of mantissa to 1 instead of 0, so it modify value although logic was is just pass a value without change. On x64 platform a xmm0 register used (SSE) and it works fine.
What is this: it is a somewhere documented undefined behavior for a NaN values or a JIT/optimization bug?
Why compiler use a FPU and SSE when no arithmetic operations was made?
Debug configuration - pass value via stack without side effects - correct result:
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
Release configuration - optimizer or a JIT does a strange pass via FPU registers and breaks a data - incorrect
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]
I just translate @HansPassant comment as an answer.
"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."
In other words, this is just a GIGO case (Garbage In, Garbage Out).
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments