当您说确定性时,我假设您想要一个可重现的模拟,每次运行模拟时都会得到完全相同的结果。
要做到这一点,您需要找到可能变化的来源并消除它。
唯一的方法是编译为特定架构的二进制文件。
浮点运算本身是完全指定的。所有现代处理器都遵循浮点标准 (IEEE-754),并且没有歧义。
主要有两种变化:
指令集的差异。这是最明显的一个。如果您将应用程序编译为 32 位或 64 位,您可能会得到略有不同的结果。32 位应用程序倾向于使用使用 80 位中间值的旧式 x87 指令。这会导致某些结果以不同的方式四舍五入。即使在 x86 上也存在差异,如果您使用 SSE 指令,它可以同时处理多个操作数。一些编译器生成的代码可能取决于操作数在内存中的对齐方式。
指令顺序的差异。在数学上,(a+b)+c
和a+(b+c)
是等价的(加法是关联的)。在浮点计算中,情况并非如此。如果a
是一、b
负一和c
一个很小的数字以便1+c
四舍五入为1
,则表达式的计算结果分别为c
和0
。编译器决定使用哪些指令。根据您的语言和平台,它可能是语言编译器或即时 IL/字节码编译器。无论哪种方式,编译器都是一个黑匣子,它可能会在我们不知情的情况下改变它编译代码的方式。最小的差异可能导致不同的最终结果。
舍入方法在理论上看起来不错,但它不起作用。无论您如何四舍五入,总有两种不同但等效的指令集产生的结果会以不同的方式四舍五入。
核心原因是四舍五入是不可组合的,从四舍五入到a
数字,然后四舍五入到b (< a)
数字不等于b
从头开始四舍五入。例如:1.49 四舍五入为 1.5,四舍五入为 0 为 2。但四舍五入为 0 则直接为 1。
因此,在基于 x87 的系统上,它使用 80 位“扩展”精度作为中间值,从 64 位有效位开始。您可以将其直接向下舍入到所需的精度。如果你有双精度中间值,你会得到相同的中间结果,但四舍五入到 53 个有效位,然后四舍五入到你想要的精度。
您唯一的选择是为特定架构生成机器代码。
现在,如果您的目标只是最小化差异而不是完全消除差异,那么答案很简单:除以或乘以 2 的幂(如 1024)不会在您使用的范围内引入任何额外的舍入误差应用程序,同时乘以和除以 1000 之类的数字。
如果您将累积错误视为随机游走,那么使用 1000 进行舍入比使用 1024 需要更多的步骤。乘法和除法都可能引入额外的错误。因此,平均而言,总误差会更大,因此舍入操作出错的可能性更大。当您对每个操作进行四舍五入时,这甚至是正确的。