JIT优化浮动问题
JIT Optimization float problems
背景
所以我正在用 C# 制作一个简单的不公平刽子手游戏(根据我提供给玩家的信息(例如字长),有一个单词列表是有效的解决方案,随着时间的推移会逐渐减少,并且玩家只有在只有一种可能的解决方案并且他们猜对了所有字母时才会获胜。
无论如何,重要的是我有一行代码如下所示:
availableWords.RemoveAll(word => AmountInCommon(word) != lowestInCommon);
(我删除了 availableWords 中与用户猜到的字母有更多共同字母的单词,而不是 availableWords 中与用户猜测的字母最少的单词)
问题是此代码仅在关闭编译器优化时有效。
展示问题的演示代码
class Program {
static void Main() {
float randomPowerOf2 = (float)Math.Pow(2, new Random().Next(-4, 5));
float foo1 = Foo(3f); // some non power of 2
float foo2 = Foo(randomPowerOf2);
float baz = Baz();
// prints false with compiler optimizations; true without compiler optimizations
Console.WriteLine(Math.Round(Foo(3f), 2) == Math.Round(foo1, 2));
// prints true with and without compiler optimizations
Console.WriteLine(Math.Round(Foo(3f), 2) == Math.Round(foo1, 2));
// prints true with and without compiler optimizations
Console.WriteLine(Foo(randomPowerOf2) == foo2);
// prints true with and without compiler optimizations
Console.WriteLine(Baz() == baz);
Console.ReadLine();
}
static float Foo(float divisor) {
return 1 / divisor;
}
static float Baz() {
return 1 / 3f;
}
}
我认为这与浮点数的工作方式有关,但令我困惑的是为什么打开和关闭编译器优化会影响它。我猜我应该做的只是 将浮点数四舍五入到 3 位左右的精度 ,因为这就是我真正需要的。为什么会发生这种情况,我的解决方案是最佳实践吗?
编辑:
我意识到在这种情况下,由于单词长度没有改变,我可以只使用 AmountInCommon
return 表示共有多少个字母的整数,而不是表示比率的浮点数与单词长度相同的字母。不过,您的回答仍然很有帮助。
短篇小说
不要比较浮点值是否相等。
程序员在发表此评论后通常做的第一件事就是推荐使用 Epsilon。然而这是错误的。 float.Epsilon
是大于零的最小可能浮点数,但是(更重要的是)它并不意味着它是任意两个任意浮点数之间的最小差异。比较浮点数时,更好的做法是选择一个合理的值来确定两个浮点数是否“足够接近”以至于相等。
您的代码存在问题,您首先对数字进行四舍五入,这可能包括以某种方式弹出 舍入 的显着差异。
说来话长
来自 ECMA 规范
9.3.7 Floating-point 类型
C# supports two floating-point types: float and double. The float and
double types are represented using the 32-bit single-precision and
64-bit double-precision IEC 60559 formats...
...
Floating-point operations may be performed with higher precision than
the result type of the operation. [Example: Some hardware
architectures support an “extended” or “long double” floating-point
type with greater range and precision than the double type, and
implicitly perform all floating-point operations using this higher
precision type.
...
还有更多
IEC 60559 标准未指定扩展 floating-point 格式参数的确切值。它仅指定最小值。自 8087 以来,Intel 处理器使用的扩展格式为 80 位长,其中 15 位用于指数,64 位用于尾数。与其他格式不同,扩展格式确实为有效数字的前导位留出了空间,从而实现了某些处理器优化等。
例子
编译器
现在出现在编译器中。编译器和抖动将尝试 优化 代码在 CPU 上执行之前的各个点,具体取决于如何设置优化(除其他外)。
这些优化将影响存储在内存中的内容、如何以及在寄存器中设置和重组的内容,以及可能会或可能不会发生的许多其他细微指令(取决于一系列因素)。这些优化中的每一个都有可能 non-deterministically 改变 浮点计算 的结果。哪个是你的确切问题。
总结
当谈到浮点运算时,永远不要依赖浮点相等。此外,不要尝试确定性地依赖编译器、抖动、您的 Cpu、体系结构、平台、版本或应用程序的 位数 的结果。
比较(对你而言)预期范围内的浮点类型。
背景
所以我正在用 C# 制作一个简单的不公平刽子手游戏(根据我提供给玩家的信息(例如字长),有一个单词列表是有效的解决方案,随着时间的推移会逐渐减少,并且玩家只有在只有一种可能的解决方案并且他们猜对了所有字母时才会获胜。
无论如何,重要的是我有一行代码如下所示:
availableWords.RemoveAll(word => AmountInCommon(word) != lowestInCommon);
(我删除了 availableWords 中与用户猜到的字母有更多共同字母的单词,而不是 availableWords 中与用户猜测的字母最少的单词)
问题是此代码仅在关闭编译器优化时有效。
展示问题的演示代码
class Program {
static void Main() {
float randomPowerOf2 = (float)Math.Pow(2, new Random().Next(-4, 5));
float foo1 = Foo(3f); // some non power of 2
float foo2 = Foo(randomPowerOf2);
float baz = Baz();
// prints false with compiler optimizations; true without compiler optimizations
Console.WriteLine(Math.Round(Foo(3f), 2) == Math.Round(foo1, 2));
// prints true with and without compiler optimizations
Console.WriteLine(Math.Round(Foo(3f), 2) == Math.Round(foo1, 2));
// prints true with and without compiler optimizations
Console.WriteLine(Foo(randomPowerOf2) == foo2);
// prints true with and without compiler optimizations
Console.WriteLine(Baz() == baz);
Console.ReadLine();
}
static float Foo(float divisor) {
return 1 / divisor;
}
static float Baz() {
return 1 / 3f;
}
}
我认为这与浮点数的工作方式有关,但令我困惑的是为什么打开和关闭编译器优化会影响它。我猜我应该做的只是 将浮点数四舍五入到 3 位左右的精度 ,因为这就是我真正需要的。为什么会发生这种情况,我的解决方案是最佳实践吗?
编辑:
我意识到在这种情况下,由于单词长度没有改变,我可以只使用 AmountInCommon
return 表示共有多少个字母的整数,而不是表示比率的浮点数与单词长度相同的字母。不过,您的回答仍然很有帮助。
短篇小说
不要比较浮点值是否相等。
程序员在发表此评论后通常做的第一件事就是推荐使用 Epsilon。然而这是错误的。 float.Epsilon
是大于零的最小可能浮点数,但是(更重要的是)它并不意味着它是任意两个任意浮点数之间的最小差异。比较浮点数时,更好的做法是选择一个合理的值来确定两个浮点数是否“足够接近”以至于相等。
您的代码存在问题,您首先对数字进行四舍五入,这可能包括以某种方式弹出 舍入 的显着差异。
说来话长
来自 ECMA 规范
9.3.7 Floating-point 类型
C# supports two floating-point types: float and double. The float and double types are represented using the 32-bit single-precision and 64-bit double-precision IEC 60559 formats...
...
Floating-point operations may be performed with higher precision than the result type of the operation. [Example: Some hardware architectures support an “extended” or “long double” floating-point type with greater range and precision than the double type, and implicitly perform all floating-point operations using this higher precision type.
...
还有更多
IEC 60559 标准未指定扩展 floating-point 格式参数的确切值。它仅指定最小值。自 8087 以来,Intel 处理器使用的扩展格式为 80 位长,其中 15 位用于指数,64 位用于尾数。与其他格式不同,扩展格式确实为有效数字的前导位留出了空间,从而实现了某些处理器优化等。
例子
编译器
现在出现在编译器中。编译器和抖动将尝试 优化 代码在 CPU 上执行之前的各个点,具体取决于如何设置优化(除其他外)。
这些优化将影响存储在内存中的内容、如何以及在寄存器中设置和重组的内容,以及可能会或可能不会发生的许多其他细微指令(取决于一系列因素)。这些优化中的每一个都有可能 non-deterministically 改变 浮点计算 的结果。哪个是你的确切问题。
总结
当谈到浮点运算时,永远不要依赖浮点相等。此外,不要尝试确定性地依赖编译器、抖动、您的 Cpu、体系结构、平台、版本或应用程序的 位数 的结果。
比较(对你而言)预期范围内的浮点类型。