我将客户端子总计算添加到我的订单页面,以便在用户进行选择时显示批量折扣。
我发现有些计算在这里或那里下降了一分钱。这不会是一个非常大的问题,除了总数与最终计算的服务器端总数(在PHP中)不匹配。
我知道舍入错误是处理浮点数时的预期结果。例如,149.95 * 0.15 = 22.492499999999996和149.95 * 0.30 = 44.98499999999999。前者按需要进行,后者则没有。
我搜索了这个主题并发现了各种各样的讨论,但没有任何令人满意的解决问题。
我目前的计算如下:
discount = Math.round(price * factor * 100) / 100;
一个常见的建议是以美分而不是美元的分数工作。但是,这需要我转换我的起始数字,围绕它们,乘以它们,舍入结果,然后将其转换回来。
本质:
discount = Math.round(Math.round(price * 100) * Math.round(factor * 100) / 100) / 100;
我想在舍入之前将数字加0.0001。例如:
discount = Math.round(price * factor * 100 + 0.0001) / 100;
这适用于我尝试过的场景,但我想知道我的逻辑。添加0.0001是否总是足够,而且永远不会太多,以强制所需的舍入结果?
注意:就我的目的而言,我只关注每个价格的单一计算(因此不会使错误复杂化),并且永远不会显示超过两位小数。
编辑:例如,我想将149.95 * 0.30的结果四舍五入到小数点后两位,得到44.99。但是,我得到44.98,因为实际结果是44.98499999999999而不是44.985。 / 100
没有引入错误。它发生在那之前。
测试:
alert(149.95 * 0.30); // yields 44.98499999999999
因此:
alert(Math.round(149.95 * 0.30 * 100) / 100); // yields 44.98
44.98预计会考虑乘法的实际结果,但不是所希望的,因为它不是用户期望的(并且与PHP结果不同)。
解决方案:我要将所有内容转换为整数来进行计算。正如公认的答案所指出的那样,我可以稍微简化一下原来的转换计算。我添加0.0001的想法只是一个肮脏的黑客。最好使用合适的工具。
答案 0 :(得分:2)
我不认为添加少量会对你有利,我猜有些情况会太多。还需要对其进行适当记录,否则可能会将其视为不正确。
以分[...]工作会要求我转换我的起始数字,围绕它们,乘以它们,然后将结果转换回来:
discount = Math.round(Math.round(price * 100) * Math.round(factor * 100) / 100) / 100;
我认为它应该只能在之后完成。但是,您应该首先将结果相乘,以便significant digits是之前两个sig数字的总和,即示例中的2 + 2 = 4个小数位:
discount = Math.round(Math.round( (price * factor) * 10000) / 100) / 100;
答案 1 :(得分:1)
在您的数字中添加少量数据将不会非常准确。您可以尝试使用库来获得更好的结果:https://github.com/jtobey/javascript-bignum。
答案 2 :(得分:1)
Bergi的回答显示了一个解决方案。这个答案显示了一个正确的数学证明。在这个过程中,它还建立了对输入中可容许多少错误的限制。
你的问题是:
假设我们再添加两个假设:
考虑值x*d*10000
。理想情况下,这将是一个整数,因为x和d每个理想地是.01的倍数,所以将x和d的理想乘积乘以10,000会产生一个整数。由于x和d中的错误很小,因此将x*d*10000
舍入为整数将产生该理想整数。例如,代替理想的x和d,我们有x和d加上小的误差,x + e0和d + e1,我们计算(x + e0)•(d + e1)•10000 =(x•d + X•E1 + d•E0 + E0•E1)•10000。我们假设e1很小,因此主要误差是d•e0•10000。我们假设e0,x中的误差小于.00004,d最多为1(100%),因此d•e0•10000小于.4。此错误加上来自e1的微小错误,不足以将x*d*10000
的舍入从理想整数更改为其他整数。 (这是因为错误必须至少为.5才能改变应该是整数轮的结果。例如,3加上.5的误差将四舍五入到4,但是加上.49999则不会。)
因此,Math.round(x*d*10000)
产生所需的整数。然后Math.round(x*d*10000)/100
是x*d*100
的近似值,其精确度远低于1美分,因此对其进行舍入,Math.round(Math.round(x*d*10000)/100)
精确地产生所需的分数。最后,除以100(产生一定数量的美元,百分之一,而不是一个美分,作为一个整数)会产生一个新的舍入误差,但是误差是如此之小,以至于当结果值正确转换为带有两位小数的十进制数,显示正确的值。 (如果使用此值执行进一步算术,则可能不会保持为真。)
从上面我们可以看出,如果x中的错误增长到.00005,则此计算可能会失败。假设订单的价值可能会增加到100,000美元。表示大约100,000的值的浮点误差最多为100,000•2 -53 。如果有人订购了十万件有这个错误的物品(他们不能,因为物品的价格会低于100,000美元,所以他们的错误会更小),价格会单独加起来,执行十万(减一)添加十万个新错误,然后我们有近二十万个错误,最多100,000个•2 -53 ,因此总误差最多为2•10 5 •10 5 •2 -53 ,约为.00000222。因此,此解决方案应适用于正常订单。
请注意,如果折扣不是整数百分比,则需要重新考虑该解决方案。例如,如果折扣被声明为“三分之一”而不是33%,那么x*d*10000
预计不会是整数。