浮点数学在C#中是否一致?是真的吗?

Blu*_*eft 152 .net c# floating-point precision ieee-754

不,这不是另一个"为什么是(1/3.0)*3!= 1"的问题.

我最近一直在读关于漂浮点的事情; 具体而言,相同的计算如何在不同的体系结构或优化设置上给出不同的结果.

对于存储重放的视频游戏来说,这是一个问题,或者是对等网络(而不是服务器 - 客户端),它依赖于每次运行程序时产生完全相同结果的所有客户端 - 一个小的差异浮点计算可能导致不同机器(甚至是同一台机器上)的游戏状态截然不同!

甚至在"跟随" IEEE-754的处理器中也会发生这种情况,这主要是因为某些处理器(即x86)使用双倍扩展精度.也就是说,它们使用80位寄存器进行所有计算,然后截断为64位或32位,导致与使用64位或32位进行计算的机器不同的舍​​入结果.

我在网上看到过这个问题的几种解决方案,但都是针对C++,而不是C#:

  • double使用_controlfp_s(Windows),_FPU_SETCW(Linux?)或fpsetprec(BSD)禁用双扩展精度模式(以便所有计算使用IEEE-754 64位).
  • 始终使用相同的优化设置运行相同的编译器,并要求所有用户具有相同的CPU架构(无跨平台播放).因为我的"编译器"实际上是JIT,它可能在每次运行程序时都有不同的优化,我认为这是不可能的.
  • 使用定点算术,避免floatdouble完全.decimal可以用于此目的,但会慢得多,并且没有任何System.Math库函数支持它.

那么,这在C#中是否也是一个问题? 如果我只打算支持Windows(而不是Mono)怎么办?

如果是,有没有办法强制我的程序以正常的双精度运行?

如果没有,是否有任何库可以帮助保持浮点计算的一致性?

Cod*_*aos 52

我知道无法在.net中确定正常浮点的确定性.允许JITter创建在不同平台(或不同版本的.net)之间表现不同的代码.因此,float在确定性.net代码中使用常规s是不可能的.

我考虑过的变通办法:

  1. 在C#中实现FixedPoint32.虽然这不是太难(我有一半完成的实现),非常小的值范围使它使用起来很烦人.你必须时刻小心,既不会溢出,也不会失去太多的精确度.最后我发现这并不比直接使用整数更容易.
  2. 在C#中实现FixedPoint64.我发现这很难做到.对于某些操作,128位的中间整数将是有用的.但.net不提供这样的类型.
  3. 实现自定义32位浮点.在实现此功能时,缺少BitScanReverse内在会导致一些烦恼.但目前我认为这是最有希望的道路.
  4. 使用本机代码进行数学运算.在每个数学运算上发生委托调用的开销.

我刚刚开始了32位浮点数学的软件实现.它在我的2.66GHz i3上每秒可以进行大约7000万次加法/乘法运算. https://github.com/CodesInChaos/SoftFloat.显然它仍然非常不完整和马车.

  • 有一个"无限"大小的整数可用BigInteger虽然没有原生int那么快或者它在那里很长,所以.NET确实提供了这样的类型(为F#创建我相信但可以在C#中使用) (2认同)
  • 如果你打算做其中的任何一个,你最好先尝试`decimal`,因为这样做要简单得多.只有当手头的任务太慢时才会考虑其他方法. (2认同)

svi*_*ick 27

C#规范(§4.1.6浮点类型)特别允许使用高于结果的精度来完成浮点计算.所以,不,我认为你不能直接在.Net中将这些计算确定为确定性.其他人建议了各种解决方法,所以你可以尝试一下.

  • 我刚刚意识到如果分发编译的程序集,C#规范并不重要.只有在想要源兼容性时才重要.真正重要的是CLR规范.但我很确定它的保证和C#保证一样弱. (9认同)
  • @IllidanS4我认为这不会保证一致的结果. (2认同)

Pet*_* O. 14

在您需要此类操作的绝对可移植性的情况下,以下页面可能很有用.它讨论了用于测试IEEE 754标准实现的软件,包括用于模拟浮点运算的软件.但是,大多数信息可能特定于C或C++.

http://www.math.utah.edu/~beebe/software/ieee/

关于定点的说明

二进制定点数也可以很好地替代浮点数,这从四个基本算术运算中可以看出:

  • 加法和减法是微不足道的.它们的工作方式与整数相同.只需加或减!
  • 要将两个固定点数相乘,请将这两个数相乘,然后向右移动定义的小数位数.
  • 要划分两个固定点数,将被除数除以定义的小数位数,然后除以除数.
  • 第四章本文对执行二进制定点数更多的指导.

二进制定点数可以在任何整数数据类型上实现,例如int,long和BigInteger,以及非CLS兼容类型uint和ulong.

如另一个答案所示,您可以使用查找表,其中表中的每个元素都是二进制定点数,以帮助实现复杂的函数,如正弦,余弦,平方根等.如果查找表的粒度小于固定点数,则建议通过将查找表的粒度的一半添加到输入来舍入输入:

// Assume each number has a 12 bit fractional part. (1/4096)
// Each entry in the lookup table corresponds to a fixed point number
//  with an 8-bit fractional part (1/256)
input+=(1<<3); // Add 2^3 for rounding purposes
input>>=4; // Shift right by 4 (to get 8-bit fractional part)
// --- clamp or restrict input here --
// Look up value.
return lookupTable[input];
Run Code Online (Sandbox Code Playgroud)

  • 您应该将其上传到开源代码项目站点,如sourceforge或github.这使得查找更容易,更容易贡献,更容易放在简历等.另外,一些源代码提示(随意忽略):使用`const`而不是`static`来表示常量,所以编译器可以优化它们; 更喜欢成员函数到静态函数(所以我们可以调用,例如``myDouble.LeadingZeros()`而不是`IntDouble.LeadingZeros(myDouble)`); 尽量避免单字母变量名称(例如,`MultiplyAnyLength`,有9个,这使得很难遵循) (5认同)
  • 有趣的是,当我在你的博客上发布时,我没有注意到博客是你的.我刚刚决定尝试谷歌+并在其C#spark它建议博客条目.所以我想"我们两个人同时开始写这样的事情真是太巧合了".但当然我们有相同的触发器:) (2认同)
  • 为什么要把它移植到Java?Java 已经通过 `strictfp` 保证了确定性的浮点数学。 (2认同)

Jon*_*son 9

这是C#的问题吗?

是.不同的体系结构是您最不担心的,不同的帧速率等可能会导致浮动表示中的不准确性 - 即使它们是相同的不准确性(例如相同的体系结构,除了一台机器上的较慢的GPU).

我可以使用System.Decimal吗?

没有理由你不能,但它的狗很慢.

有没有办法强制我的程序以双精度运行?

是.自己托管CLR运行时 ; 并在所有nessecary电话/标志编译调用CorBindToRuntimeEx之前(即改变浮点运算的行为)到C++应用程序.

是否有任何库可以帮助保持浮点计算的一致性?

从来没听说过.

还有另一种解决方法吗?

我以前解决了这个问题,想法是使用QNumbers.它们是一种定点的实物形式; 但不是基数为10(十进制)的固定点 - 而是基数为2(二进制); 因此,它们上的数学基元(add,sub,mul,div)比天真的基础10固定点快得多; 特别是如果n两个值相同(在您的情况下它将是).此外,因为它们是完整的,所以它们在每个平台上都有明确的结果.

请记住,帧率仍然可以影响这些,但它并没有那么糟糕,并且很容易使用同步点进行纠正.

我可以在QNumbers中使用更多数学函数吗?

是的,往返十进制来做到这一点.此外,你应该真正使用查找表的trig(sin,cos)函数; 因为那些可以在不同的平台上真正给出不同的结果 - 如果你正确编码它们可以直接使用QNumbers.

  • 不确定你所说的帧速率是什么问题.显然,您希望获得固定的更新速率(例如参见[此处](http://gamedev.stackexchange.com/questions/15192)) - 无论是否与display-framerate相同都无关紧要.只要所有机器上的不准确性都相同,我们就会很好.我根本不明白你的第三个答案. (3认同)

Nat*_*ead 6

根据这个稍微陈旧的MSDN博客条目,JIT不会使用SSE/SSE2作为浮点数,它都是x87.因此,正如您所提到的,您必须担心模式和标志,并且在C#中无法控制.因此,使用正常的浮点运算并不能保证程序的每台机器都具有完全相同的结果.

为了获得双精度的精确再现性,您将不得不进行软件浮点(或定点)仿真.我不知道C#库这样做.

根据您需要的操作,您可以单精度地逃脱.这是个主意:

  • 以单精度存储您关心的所有值
  • 执行操作:
    • 将输入扩展到双精度
    • 以双精度进行操作
    • 将结果转换回单精度

x87的一大问题是计算可能以53位或64位精度完成,具体取决于精度标志以及寄存器是否溢出到内存.但是对于许多操作来说,以高精度执行操作并回归到较低精度将保证正确答案,这意味着所有系统上的答案将保证相同.无论您获得额外的精度都无关紧要,因为您有足够的精度来保证在任何一种情况下都能得到正确答案.

应该在此方案中运行的操作:加法,减法,乘法,除法,sqrt.像sin,exp等的东西不起作用(结果通常会匹配,但不能保证). "什么时候双重四舍五入无害?" ACM参考(付费注册请求)

希望这可以帮助!

  • 这也是.NET 5或6或42可能不再使用x87计算模式的问题.标准中没有任何要求它. (2认同)

Yah*_*hia 5

正如其他答案已经指出的那样:是的,这是 C# 中的一个问题 - 即使在保持纯 Windows 时也是如此。

至于解决方案:如果您使用内置BigInteger类并通过对此类数字的任何计算/存储使用公分母将所有计算缩放到定义的精度,则可以减少(并通过一些努力/性能损失)完全避免问题。

根据 OP 的要求 - 关于性能:

System.Decimal用 1 位表示符号和 96 位整数和一个“小数位数”(表示小数点所在的位置)的数字。对于您进行的所有计算,它必须对这个数据结构进行操作,并且不能使用 CPU 中内置的任何浮点指令。

BigInteger“办法”做类似的东西-只是你可以定义你有多少位需要/想......也许你只想要80位或精度240位。

缓慢总是来自于必须通过仅整数指令模拟对这些数字的所有操作,而不使用 CPU/FPU 内置指令,这反过来导致每个数学运算的指令更多。

为了减少对性能的影响,有几种策略——比如 QNumbers(参见 Jonathan Dickinson 的回答——浮点数学在 C# 中是否一致?可以吗?)和/或缓存(例如触发计算......)等。