我们正在将应用程序从 .NET Framework 4.8 更新到 .NET 8。
在回归测试期间,我们注意到隐式加宽转换似乎以不同的顺序发生,导致结果发生一些变化。
在.NET Framework中,像这样的操作:
d1= f1 * f2
将在执行乘法之前将f1
和f2
转换为双倍,而在.NET 8中将首先进行浮点数之间的乘法,并且那么就会发生扩大。我知道浮点二进制数学的行为。我并不是试图声称其中之一是“错误的” - 更多只是试图理解为什么行为会改变。
并且:
有什么方法可以暂时将 .NET 8 行为更改回 .NET Framework 行为,以便我们可以更好地理解我们的回归测试吗? (P.S.是的,我知道如果我们一开始就没有隐式转换,这不会成为问题。但这是一个大型遗留代码库,我无法轻易更改。)
控制台应用程序测试代码:
Console.WriteLine(".NET 8");
Console.WriteLine("Input Float: 0.3333333F");
float f1 = 0.3333333F;
Console.WriteLine("Decimal: " + f1.ToString());
Console.WriteLine("Binary: " + GetFloatBinary(f1));
Console.WriteLine();
Console.WriteLine("Float multiplication first, then conversion");
Console.WriteLine("f2 = f1 * f1");
float f2 = f1 * f1;
Console.WriteLine("Decimal: " + f2.ToString());
Console.WriteLine("Binary: " + GetFloatBinary(f2));
Console.WriteLine("d1 = (double)f2");
double d1 = (double)f2;
Console.WriteLine("Decimal: " + d1.ToString());
Console.WriteLine("Binary: " + GetDoubleBinary(d1));
Console.WriteLine();
Console.WriteLine("Conversion first, multiplication second");
Console.WriteLine("d2 = (double)f1 * (double)f1");
double d2 = (double)f1 * (double)f1;
Console.WriteLine("Decimal: " + d2.ToString());
Console.WriteLine("Binary: " + GetDoubleBinary(d2));
Console.WriteLine();
Console.WriteLine("Let the platform decide");
Console.WriteLine("d3 = f1 * f1");
double d3 = f1 * f1;
Console.WriteLine("Decimal: " + d3.ToString());
Console.WriteLine("Binary: " + GetDoubleBinary(d3));
Console.WriteLine();
Console.ReadLine();
static string GetFloatBinary(float value)
{
const int bitCount = sizeof(float) * 8;
int intValue = System.BitConverter.ToInt32(BitConverter.GetBytes(value), 0);
return Convert.ToString(intValue, 2).PadLeft(bitCount, '0');
}
static string GetDoubleBinary(double value)
{
const int bitCount = sizeof(double) * 8;
int intValue = System.BitConverter.ToInt32(BitConverter.GetBytes(value), 0);
return Convert.ToString(intValue, 2).PadLeft(bitCount, '0');
}
结果:
.NET FRAMEWORK
Input Float: 0.3333333F
Decimal: 0.3333333
Binary: 00111110101010101010101010101010
Float multiplication first, then conversion
f2 = f1 * f1
Decimal: 0.1111111
Binary: 00111101111000111000111000110111
d1 = (double)f2
Decimal: 0.111111097037792
Binary: 0000000000000000000000000000000011100000000000000000000000000000
Conversion first, multiplication second
d2 = (double)f1 * (double)f1
Decimal: 0.111111097865635
Binary: 0000000000000000000000000000000011100011100011100011100100000000
Let the platform decide
d3 = f1 * f1
Decimal: 0.111111097865635
Binary: 0000000000000000000000000000000011100011100011100011100100000000
.NET 8
Input Float: 0.3333333F
Decimal: 0.3333333
Binary: 00111110101010101010101010101010
Float multiplication first, then conversion
f2 = f1 * f1
Decimal: 0.1111111
Binary: 00111101111000111000111000110111
d1 = (double)f2
Decimal: 0.1111110970377922
Binary: 0000000000000000000000000000000011100000000000000000000000000000
Conversion first, multiplication second
d2 = (double)f1 * (double)f1
Decimal: 0.11111109786563489
Binary: 0000000000000000000000000000000011100011100011100011100100000000
Let the platform decide
d3 = f1 * f1
Decimal: 0.1111110970377922
Binary: 0000000000000000000000000000000011100000000000000000000000000000
我想我清楚地了解发生了什么变化,但我找不到任何文档说明原因。
感谢您的见解!
两者的 IL 是相同的,并且 ECMA 附录中没有提及,因此可能在这方面没有真正的变化。
在 64 位上,.NET Framework 与我的情况下的 .NET 6 相同(并且结果与所讨论的结果相同) - 首先乘以浮点数。
所以,我认为这是期望的行为。
在 32 位上,.NET Framework 的反汇编是:
00120AD2 D9 45 C0 fld dword ptr [ebp-40h]
00120AD5 D8 C8 fmul st,st(0)
00120AD7 DD 5D A4 fstp qword ptr [ebp-5Ch]
关于
fld
说明: fld 指令加载 32 位、64 位或 80 位浮点 值入栈。该指令
转换 32 位和 64 位 在压入该值之前将操作数转换为 80 位扩展精度值 到浮点堆栈上。所以这似乎是罪魁祸首,可能是他们后来在 .NET(核心)中修复的错误。