分割浮点数几乎相等,没有损失

时间:2014-10-28 04:05:18

标签: c++ c floating-point

假设浮点数的十进制可视化表示后的2位数(例如:货币),以最小总误差分割金额的最佳方法是什么?

例如,要将100.00平均分配给3个银行账户,我会做这样的事情:

double amount = 100.0;
double acc1 = amount / 3.0;
double acc2 = amount / 3.0;
double acc3 = amount / 3.0;

但是,当每个帐户余额打印2位小数时,我得到:

printf("%.2f\n", acc1);
printf("%.2f\n", acc2);
printf("%.2f\n", acc3);

33.33
33.33
33.33

很明显总结了账户的所有金额为99.99,由于四舍五入而损失了0.01。

理想情况下,我想要一些可以分布几乎同等且可视化打印的功能/算法

33.34
33.33
33.33

三个账户中哪一个获得额外的0.01无关紧要。

我该怎么做?是否有任何舍入算法名称?

3 个答案:

答案 0 :(得分:6)

你在这里犯了多个错误。 double是双精度二进制浮点数。 100.0 / 3.0等于33.33333333333333570180911920033395290374755859375;给予每个人100.0/3.0的问题并不在于你给每个人的价格略低于蛋糕的总量,而是你试图给每个人提供比你更多的蛋糕。然后将其舍入到两个小数位,这是一个您无法合理预期保留总和的操作。

我建议您在应用程序中使用整数美分而不是浮点数美元进行广告投放。

话虽如此,要将大小为C的蛋糕分成浮点部分,以便在n人之间进行分发,您可以为n-1人提供大小C/n件最后一个人是一块fma(-C/n, n-1, C)。这里需要融合乘法加法,因为(C/n)*(n-1)中的乘法可能导致舍入误差。也可以使用fmodremainder来完成此操作。

答案 1 :(得分:2)

使用floating-point numbers代表货币只是asking for trouble;整数更适合这个目的。

对于此问题,您希望首先将原始总和x分发给n人使用分层分割(将向下分配到最近的 ):

int q = x / n;

(这里我假设整数算术与美分。)对于x = 10000n = 3,你计算商q = 10000 / 3 = 3333和把它交给每个人:

33.33
33.33
33.33

然后,计算余数:

int r = x % n;

并将剩余的r美分分发给r人(分别给他们一个)。由于r总是小于人数,因此您需要决定谁将成为幸运的赢家。

在示例r = 10000 % 3 = 1中,我们只有一分钱只能给一个人:

33.34 = 33.33 + 0.01
33.33
33.33

您也可以使用显式公式直接计算。第i个人收到的分数由下式给出:

y[i] = q + (i < r);

其中0 ≤ i < n

答案 2 :(得分:0)

由于在浮点运算中加法甚至不是关联的,所以你的问题没有明确定义......
嗯,它可能是明确定义的,请参阅更新2,但一些天真的期望可能会导致意外

如果你有(x + y)+ z == sum,那么x +(y + z)== sum

的担保就不多了

例如,假设你想要分成0.31 in 3 IEEE 754双精度,0.10,0.10和0.11
这里是计算的总和(最小的小数部分将舍入到与总和相同的浮点数)

0.10+0.10+0.11 == 0.31
0.11+0.10+0.10 == 0.31000000000000005

这两个数字非常接近,但不一样,没有人真的等于31/100但是...... 在第二种形式中,您可以稍作修正:

0.10999999999999999 + 0.10 + 0.10 == 0.31

这意味着根据您用来汇总的顺序,结果会有所不同......
那么你对浮点的期望是什么呢?

当然,如果您确定舍入错误永远不会危险地累积,您可以忽略这些微小的差异并继续使用浮点来执行必要的商/余数运算;它可以使用浮动,但你应该质疑每一个天真的期望,它可能会变得棘手。我的建议是像其他人建议的那样简单地使用整数运算。

<强> 更新

从PascalCuoq读取链接Rounding floats so that they sum to precisely 1后,我意识到0.10999999999999999在所有情况下都是比0.11更好的解决方案,因为它是0.31-(0.31-0.11),因为0.31-(0.31-0.1)== 0.1 。谢谢PascalCuoq!

更新2

非关联性是必不可少的,因为它意味着两种不同的天真实现会导致不同的结果。

然而,正如tmyklebu所强调的那样,确切的总和已明确定义,但在C / C ++中不易获取。

在上面的例子中,最接近的10到100倍,10 / 100,11 / 100的精确总和略微超过最接近的倍数到31/100,所以我们必须用ulp调整其中一个操作数。这就是浮点运算的可争论性超过了关联性的单一属性。

如果我们试图在3倍中公平地分配双倍数量= 0.30,这可以说明:因为最接近的30/100的双精度正好是5404319552844595/18014398509481984,并且因为分子不能被3整除(余数为1) ),然后它不能分成3个相等的双打。 5404319552844595/3除以18014398509481984的商将导致前一个0.1,我们必须通过增加ulp来调整一个操作数。下面的天真总和匹配完全总和:

0.09999999999999999+0.09999999999999999+0.1 == 0.3 

这是众所周知的另一种形式(至少在SO上)0.1 + 0.1 + 0.1!= 0.3