在用 double 计算货币值时,出现一美分错误所需的最少浮点运算次数是多少?

What is the minimum number of floating-point operations needed to get a one-cent error when computing monetary values with double?

仅对 1 万亿美元或更少 (1e12 USD) 的货币值执行一系列算术运算 (+,-,*,/,round),四舍五入到最接近的美分。镜像这些操作导致值出现一美分或更多舍入误差的最小双精度浮点操作数是多少?

实际上,在对双精度数进行四舍五入计算结果时,执行多少操作是安全的?

此问题与 Why not use Double or Float to represent currency? 有关,但寻求使用双精度浮点问题的具体示例,目前在该问题的任何答案中都找不到。

当然,double 值必须在比较之前四舍五入,例如 ==、<、>、<=、>= 等。并且 double 值必须四舍五入才能显示。但是这个问题问的是,在对正在执行的计算类型进行现实限制的情况下,您可以将双精度值保持多长时间而不冒舍入误差的风险。

此问题与问题 Add a bunch of floating-point numbers with JavaScript, what is the error bound on the sum? 类似,但在允许乘法和除法方面限制较少。坦率地说,我可能对这个问题的限制太小了,因为我真的希望有一个在普通业务中可能发生的舍入误差的例子。


的扩展讨论中已经清楚地表明,由于在操作中包含“round”,这个问题的表述不当。

我觉得偶尔四舍五入到最接近的美分的能力很重要,但我不确定如何最好地定义该操作。

同样,我认为四舍五入到最接近的美元可能是合理的,例如,在这样的税收环境中(谁知道是什么原因)实际上鼓励这种四舍五入,尽管美国税法没有要求。

但我发现当前的 令人不满意,因为感觉好像先进行四舍五入后进行银行四舍五入仍会产生正确的结果。

最多三个。

据推测,使用的是 IEEE-754 binary64,也称为“双精度”。

.29 四舍五入为 0.289999999999999980015985556747182272374629974365234375。乘以 50 产生 14.4999999999999982236431605997495353221893310546875,之后 round 产生 14。然而,使用实数运算,.29•50 将是 14.5 并四舍五入为 15。(回想一下 round 函数被指定为四舍五入中途案例远离零。)

前面使用四舍五入到整数。下面是一个使用四舍五入到最接近的“分”的示例,即小数点后两位数。使用 IEEE-754 binary64 语义的 C 实现与此程序的舍入到最近关系:

#include <math.h>
#include <stdio.h>


int main(void)
{
    printf(".55 -> %.99g.\n", .55);
    printf(".55/2 -> %.99g.\n", .55/2);
    printf("Rounded to two digits after decimal point -> %.2f.\n", .55/2);
    printf("1.15 -> %.99g.\n", 1.15);
    printf("1.15/2 -> %.99g.\n", 1.15/2);
    printf("Rounded to two digits after decimal point -> %.2f.\n", 1.15/2);
}

产生这个输出:

.55 -> 0.5500000000000000444089209850062616169452667236328125.
.55/2 -> 0.27500000000000002220446049250313080847263336181640625.
Rounded to two digits after decimal point -> 0.28.
1.15 -> 1.149999999999999911182158029987476766109466552734375.
1.15/2 -> 0.5749999999999999555910790149937383830547332763671875.
Rounded to two digits after decimal point -> 0.57.

除法的实数结果为 .275 和 .575。任何普通的舍入到最近的决胜规则都会以相同的方向舍入它们(向上产生 .28 和 .58,向下产生 .27 和 .57,甚至产生 .28 和 .58)。但是 IEEE-754 binary64 结果产生的结果在不同的方向上四舍五入,一个向上,一个向下。因此,无论选择哪个决胜规则,其中一个浮点结果与所需的实数结果不匹配。

只需将一分钱除以二,四舍五入(银行家四舍五入为零)然后乘以 2,即

round(0.01 / 2, 2) * 2 

round 的第二个参数指示四舍五入为整数便士,然后结果为零。

请注意,有一些 disasters (see also here),由于四舍五入不正确,包括温哥华证券交易所的指数崩溃。

此外,请注意,在某些金融应用程序中需要低于 1 美分的簿记,例如,在某些证券交易所,低至 0.0001 美元,如本网站上的 filing. Some additional info in this Quantitative Finance question, and this

A sequence of arithmetic operations (+,-,*,/,round) are performed on only monetary values of 1 trillion dollars or less (1e12 USD), rounded to the nearest penny.

前提有问题,因为只有 $xxx,xxx,xxx,xxx,xxx.yy 值,其中 .yy 是 .00, .25, .50, .75 可以满足双倍的要求。所有其他值都没有真正四舍五入到最接近的便士,只是接近。让我们假设货币变量总是四舍五入到最接近的 0.01 ,因为它们可以 最好用 double.

表示

如果使用 1.0 为 1.00 美元,万亿美元范围内的 + 或 - 资金,1.0e12(1 万亿美元)的 unit in the last place 为 .0001220703125。 一分钱 的价值可能会减少 0.00006103515625 或大约 1/164 美分。很容易推断,与十进制数学相比,将大约 164 个这样的值相加可能会产生 1 美分的误差。

对于 * 的钱,将 2 笔钱乘以一个因子(比如利率)是没有意义的。给定利率可以是任何 double,一个简单的 round_to_the_cent(money * rate) 可以很容易地与小数货币相比减少 1 美分。


用 1 次乘法和 1 次轮差 $0.01 的例子

考虑涉及一些 M * rate 的金钱计算,在纸面上,其乘积为 $xxxxxx.yy5,而 Mrate 不是 正好 可以用 double 表示。在纸面上,它四舍五入为 $xxxxxx.yy0 或 $xxxxxx.yy0 + 0.01。对于 double,它是一个 coin flip,它将与一分钱匹配。

int main() {
  double money = 1000.05;
  double rate = 1.90; // 170 %
  double product = money * rate;
  printf("Decimal precise  : 00.095\n");
  printf("Computer precise : $%.17f\n", product);
  printf("Decimal rounded  : 00.10\n");  // Ties to even, or ties away
  printf("Computer rounded : $%.2f\n", product);
}

输出

Decimal precise  : 00.095
Computer precise : 00.09499999999979991
Decimal round    : 00.10
Computer round   : 00.09

IAC,再等几年。据说C2x会提供十进制浮点数类型。