1

我正在尝试构建一个确定性模拟,其中浮点数通过以下函数被截断:(我在这里找到它:http: //joshblog.net/2007/01/30/flash-floating-point-number-错误

return Math.round(10000 * float) / 10000;

我的问题是:我将它除以 10000 的事实本身不是浮点错误的来源吗? IE 每次发生除法时,都会产生一个新的浮点数,并可能产生更多不确定的结果。

编辑:这个怎么样?仅使用 2 的幂

return Math.round(float* 1024) / 1024;
4

4 回答 4

2

当您说确定性时,我假设您想要一个可重现的模拟,每次运行模拟时都会得到完全相同的结果。

要做到这一点,您需要找到可能变化的来源并消除它。

唯一的方法是编译为特定架构的二进制文件。

浮点运算本身是完全指定的。所有现代处理器都遵循浮点标准 (IEEE-754),并且没有歧义。

主要有两种变化:

  1. 指令集的差异。这是最明显的一个。如果您将应用程序编译为 32 位或 64 位,您可能会得到略有不同的结果。32 位应用程序倾向于使用使用 80 位中间值的旧式 x87 指令。这会导致某些结果以不同的方式四舍五入。即使在 x86 上也存在差异,如果您使用 SSE 指令,它可以同时处理多个操作数。一些编译器生成的代码可能取决于操作数在内存中的对齐方式。

  2. 指令顺序的差异。在数学上,(a+b)+ca+(b+c)是等价的(加法是关联的)。在浮点计算中,情况并非如此。如果a是一、b负一和c一个很小的数字以便1+c四舍五入为1,则表达式的计算结果分别为c0。编译器决定使用哪些指令。根据您的语言和平台,它可能是语言编译器或即时 IL/字节码编译器。无论哪种方式,编译器都是一个黑匣子,它可能会在我们不知情的情况下改变它编译代码的方式。最小的差异可能导致不同的最终结果。

舍入方法在理论上看起来不错,但它不起作用。无论您如何四舍五入,总有两种不同但等效的指令集产生的结果会以不同的方式四舍五入。

核心原因是四舍五入是不可组合的,从四舍五入到a数字,然后四舍五入到b (< a)数字不等于b从头开始四舍五入。例如:1.49 四舍五入为 1.5,四舍五入为 0 为 2。但四舍五入为 0 则直接为 1。

因此,在基于 x87 的系统上,它使用 80 位“扩展”精度作为中间值,从 64 位有效位开始。您可以将其直接向下舍入到所需的精度。如果你有双精度中间值,你会得到相同的中间结果,但四舍五入到 53 个有效位,然后四舍五入到你想要的精度。

您唯一的选择是为特定架构生成机器代码。

现在,如果您的目标只是最小化差异而不是完全消除差异,那么答案很简单:除以或乘以 2 的幂(如 1024)不会在您使用的范围内引入任何额外的舍入误差应用程序,同时乘以和除以 1000 之类的数字。

如果您将累积错误视为随机游走,那么使用 1000 进行舍入比使用 1024 需要更多的步骤。乘法和除法都可能引入额外的错误。因此,平均而言,总误差会更大,因此舍入操作出错的可能性更大。当您对每个操作进行四舍五入时,这甚至是正确的。

于 2012-07-06T21:43:59.023 回答
2

我的目标是跨平台(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; 尽管它们都不是完美的。

这是一个准确的说法吗?

于 2012-07-08T14:05:32.763 回答
0

假设 IEEE 754 二进制浮点算法在舍入到最近模式下,除以 10,000 会导致舍入误差等于精确数学结果与可表示为双精度的最接近数之间的差。此误差最多为结果的 1/2 ULP(最低精度单位)。

乘以 2 的幂,舍入为整数,除以 2 的相同幂不会导致舍入操作中的任何错误,除了: 乘以 2 1024左右的精确结果(精确阈值稍慢)或更大将产生浮点无穷大。(一般而言,当结果下溢浮点范围时,即当精确的数学结果在 (0, 2 -1022 ) 中时,乘以或除以 2 的幂会产生舍入误差。但是,在以下情况下不会发生下溢为 p 计算 round(x*p)/p 的某个正幂 2 小于 2 1023。)

以这种方式量化数字通常不会产生确定性的结果。当预量化值有错误可能跨越量子之间的中点时,可能会出现两个平台之间的偏差。

于 2012-07-08T11:01:47.233 回答
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;
}
于 2012-07-08T13:53:26.297 回答