我正在尝试构建确定性模拟,其中浮点数通过以下函数被截断: (我在这里找到了:http://joshblog.net/2007/01/30/flash-floating-point-number-errors)
return Math.round(10000 * float) / 10000;
我的问题是:我不是将它除以10000,这本身就是浮点错误的来源吗? IE每次发生分割时,都会产生一个新的浮点数,并带来更多可能的不确定性结果。
编辑: 这个怎么样?仅使用2的幂
return Math.round(float* 1024) / 1024;
答案 0 :(得分:3)
我的目标是在不同平台(C#/ AS3和32/64位)上实现更高一致性,同时我接受100%一致性是不可能的。 (由于AS3不能进行实数整数运算,因为所有内部运算都是通过浮点数执行的)
到目前为止我收集的内容(感谢Eric Postpischil和Jeffrey Sax):
Math.round(1024 * float) / 1024;
如上所述,“ Math.round(1024 * float)”操作可能无法在所有平台上产生相同的结果,如果“错误累积到一半以上量子“甚至可以”在单个操作中“。
对于“/ 1024”部分,因为1024是2的幂,即直位移位,它不会引入额外的错误,其中好像我除以1000会导致额外错误的可能性很小,因为1000不能完美地表示。所以1000分组可能会在舍入后引入另一个错误,1024除法不能。
结论: Math.round(1024 * float)/ 1024;比Math.round(1000 * float)/ 1000更好;虽然它们都不是完美的。
这是一个准确的陈述吗?
答案 1 :(得分:2)
当你说确定性时,我假设你想要一个可重现的模拟,每次运行模拟时都会获得完全相同的结果。
为了实现这一目标,您需要找到可能的变化来源并将其消除。
唯一的方法是为特定架构编译为二进制文件。
浮点运算本身已完全指定。所有现代处理器都遵循浮点标准(IEEE-754),不会产生歧义。
主要有两种变体:
指令集的差异。这是最明显的一个。如果将应用程序编译为32位或64位,则结果可能略有不同。 32位应用程序倾向于使用使用80位中间值的旧式x87指令。这会导致某些结果的舍入方式不同。即使在x86上也存在差异,如果使用SSE指令,它们可以同时处理多个操作数。有些编译器可能会生成代码,这些代码取决于操作数在内存中的对齐方式。
指令排序的差异。在数学上,(a+b)+c
和a+(b+c)
是等效的(加法是 associative )。在浮点计算中,情况并非如此。如果a
为1,b
为减1,c
为小数,以便1+c
舍入为1
,则表达式求值为{{分别为1}}和c
。编译器决定使用哪些指令。根据您的语言和平台,它可能是语言编译器或即时IL /字节码编译器。无论哪种方式,编译器都是一个黑盒子,它可能会改变它在我们不知情的情况下编译代码的方式。最小的差异可能导致不同的最终结果。
舍入方法在理论上看起来不错,但它不起作用。无论你如何舍入,总会出现两种不同但等价的指令集产生不同舍入结果的情况。
核心原因是舍入是不可组合的,因为舍入到0
个数字,然后舍入到a
个数字并不等于舍入到b (< a)
个数字。开始。例如:1.49舍入到一位数是1.5并将其舍入到零位给出2.但是舍入到零数字直接产生1。
因此,在基于x87的系统中,对于中间值使用80位“扩展”精度,从64位有效位开始。您可以直接将其向下舍入到所需的精度。如果您有双精度中间体,则得到相同的中间结果,但舍入到53个有效位,然后四舍五入到您想要的精度。
您唯一的选择是为特定架构生成机器代码。
现在,如果您的目标只是最小化差异而不是完全消除它们,那么答案很简单:除以或乘以2的幂(如1024)不会在范围内引入任何额外的舍入误差您的应用程序使用,乘以除以1000之类的数字。
如果将累积错误视为随机游走,则使用1000进行舍入需要比使用1024更多的步骤。乘法和除法都可能引入其他错误。所以平均而言,总误差会更大,因此舍入操作出错的可能性更大。当你完成每个操作时,这甚至都是正确的。
答案 2 :(得分:0)
除以10,000导致舍入误差等于精确数学结果与以双精度表示的最接近数字之间的差异,假设IEEE 754二进制浮点运算在舍入到最接近模式。此错误最多为结果的1/2 ULP(最低精度单位)。
乘以2的幂,舍入为整数,除以2的相同幂将不会导致舍入操作中出现任何错误,除了:在2 1024 附近得到精确结果(确切的阈值稍慢)或更大将产生浮点无穷大。 (通常,当结果下溢浮点范围时,即,当精确的数学结果在(0,2 -1022 )时,乘以2除以2的幂会产生舍入误差。但是,当计算圆(x * p)/ p时,p的某些正幂小于2 1023 时,不会发生下溢。)
以这种方式量化数字通常不会产生确定性结果。当预量化值有误差时,可能会发生两个平台之间的偏差,这可能会跨越量子之间的中点。
答案 3 :(得分:0)
以下代码证明,即使在缩放中没有错误,舍入到量子的倍数也不会产生确定性结果。
我得到的输出是:
Machine 0 produces 0x1p+0 (1).
Machine 1 produces 0x1.004p+0 (1.0009765625).
The results differ.
源代码是:
#include <stdio.h>
#include <math.h>
// Round a value to the nearest multiple of the quantum.
static double Quantize(double x)
{
static const double Quantum = 1024., InverseQuantum = 1/Quantum;
return round(x * Quantum) * InverseQuantum;
}
int main(void)
{
/* For this example, we are in the middle of some calculation, where we
have some value a from earlier operations. a0 and a1 represent the
calculated values of a on two different platforms. Observe that the
difference is as small as possible, just a single ULP.
*/
double a0 = 0x1.cbd9f42000000p0;
double a1 = 0x1.cbd9f42000001p0;
// Define a constant that the calculation uses.
double b = 0x1.1d2b9fp-1;
// Calculate the pre-quantization result on each machine.
double x0 = a0 * b;
double x1 = a1 * b;
// Quantize the result on each machine.
double y0 = Quantize(x0);
double y1 = Quantize(x1);
// Display the results.
printf("Machine 0 produces %a (%.53g).\n", y0, y0);
printf("Machine 1 produces %a (%.53g).\n", y1, y1);
printf("The results %s.\n", y0 == y1 ? "are identical" : "differ");
return 0;
}