JavaScript中的舍入不会给予与PHP中相同的结果?

8ulbf1ek  于 2023-08-02  发布在  PHP
关注(0)|答案(2)|浏览(106)

我想我试过所有的例子从网上!这是我的PHP代码:

echo(number_format(22.95 * 4.1, 2));

字符串
结果是94.10 with PHP。我一直试图用JavaScript得到同样的结果,但无论我怎么尝试,它总是94.09。有什么办法吗?
我试过的一些代码:

var x = 22.95;
var y = 4.10;
var res = x * y;
var res1 = res.toFixed(3);
console.log(res);
console.log(res1);
console.log(Number(res1).toFixed(2));
console.log(Math.round(res * 100) / 100);


谢谢你,谢谢

vxf3dgd4

vxf3dgd41#

PHP number_format()比JS Number.prototype.toFixed()做的更多,因此,你不能把它当作替身。
除了在基数点前面设置十进制和大数分隔符的参数之外,还包括基数点。
更不用说您可能会以移植函数而告终(参见. What is the JS equivalent to the PHP function number_format?)。提示:给予他们你的号码作为一个新的测试用例。
对于94.10 vs. 94.09,这是在 * Is floating point math broken? * 中的问答,正如我们已经发现JavaScript中缺少舍入,问题陈述如下:

> 22.95 * 4.1 != 94.095
true

字符串
PHP也是如此:

> 22.95 * 4.1 != 94.095
= true


有道理,最后一位的0.5是一个正好介于两个以2为基数的数之间的数。
现在的问题是,如果仍然可以将数字传送到格式上,以便以足够的精度显示,以便内部应用任何浮点计算Number.prototype.toFixed(),不要把浮点精度误差加起来,以至于剩下的精度太少,无法用两个数字来显示它的近似值?
阅读number_format()是 “使用舍入半向上规则”,在传递给toFixed(2)之前舍入¹可能已经足够你的用例了。
基数点后三位数的精度,加上 “必要时舍入”,应足以显示基数点后的两位数:

> Math.round((22.95 * 4.10).toFixed(3) * 100) / 100
94.1


这个数字应该有足够的太小精度错误,以便Number.prototype.toFixed()的两个数字的字符串格式仍然正确地格式化字符串:

> (Math.round((22.95 * 4.10).toFixed(3) * 100) / 100).toFixed(2)
'94.10'


是的,成功了。幸运的是!因为n / 100在被传递给toFixed(2)之前再次添加了浮点精度错误,这会增加更多的错误!
也许:

/**
 * @param {number} num
 * @param {number} decimals natural number of digits (incl. zero) after the radix point
 * @return {string} rounded num using round up rule with decimals after the radix point
 */
function roundUpRule(num, decimals = 0) {
    let factor = num < 0 ? -1 : 1;
    return (Math.round(parseFloat((num * factor).toFixed(1+decimals)) * `1E+${decimals}`) / `1E+${decimals}` * factor).toFixed(decimals);
}


正如Edward在2016年11月所写:
在使用小数位移位和舍入方法正确舍入后,可以使用number.toFixed(x)方法将其转换为具有所需数量的零的字符串。例如,使用跨浏览器方法将1.34舍入为1.3,然后添加1零,并使用1.3.toFixed(2)转换为字符串(以获得“1.30”)
如果这个肮脏的快捷方式还不够,最后的办法是只对字符串应用舍入规则,例如:

> (22.95 * 4.10).toFixed(62)
'94.09499999999998465227690758183598518371582031250000000000000000'
'94.0949999999999846522769075818359851837158203125000000000000000'
'94.094999999999984652276907581835985183715820312500000000000000'
'94.09499999999998465227690758183598518371582031250000000000000'
'94.0949999999999846522769075818359851837158203125000000000000'
'94.094999999999984652276907581835985183715820312500000000000'
'94.09499999999998465227690758183598518371582031250000000000'
'94.0949999999999846522769075818359851837158203125000000000'
'94.094999999999984652276907581835985183715820312500000000'
'94.09499999999998465227690758183598518371582031250000000'
'94.0949999999999846522769075818359851837158203125000000'
'94.094999999999984652276907581835985183715820312500000'
'94.09499999999998465227690758183598518371582031250000'
'94.0949999999999846522769075818359851837158203125000'
'94.094999999999984652276907581835985183715820312500'
'94.09499999999998465227690758183598518371582031250'
'94.0949999999999846522769075818359851837158203125'
'94.094999999999984652276907581835985183715820313'
'94.09499999999998465227690758183598518371582031'
'94.0949999999999846522769075818359851837158203'
'94.094999999999984652276907581835985183715820'
'94.09499999999998465227690758183598518371582'
'94.0949999999999846522769075818359851837158'
'94.094999999999984652276907581835985183716'
'94.09499999999998465227690758183598518372'
'94.0949999999999846522769075818359851837'
'94.094999999999984652276907581835985183'
'94.09499999999998465227690758183598518'
'94.0949999999999846522769075818359852'
'94.094999999999984652276907581835985'
'94.09499999999998465227690758183599'
'94.0949999999999846522769075818360'
'94.094999999999984652276907581836'
'94.09499999999998465227690758184'
'94.0949999999999846522769075818'
'94.094999999999984652276907582'
'94.09499999999998465227690758'
'94.0949999999999846522769076'
'94.094999999999984652276908'
'94.09499999999998465227691'
'94.0949999999999846522769'
'94.094999999999984652277'
'94.09499999999998465228'
'94.0949999999999846523'
'94.094999999999984652'
'94.09499999999998465'
'94.0949999999999847'
'94.094999999999985'
'94.09499999999999'
'94.0950000000000'
'94.095000000000'
'94.09500000000'
'94.0950000000'
'94.095000000'
'94.09500000'
'94.0950000'
'94.095000'
'94.09500'
'94.0950'
'94.095'
'94.10'


如果需要这样做,那么重构roundUpRule()。
¹ Cf. How to round to at most 2 decimal places, if necessary

讨论

十进制数字系统不仅不能表示所有的数字,而且当你准备用有限的小数来显示计算结果时,例如:第二,正如你的问题,你也需要经常地做四舍五入,如果不是经常。
number_format()作为一个用于显示目的的函数已经完成了这个舍入。
当你在计算机系统上进行计算时,你把数字写成小数,计算机已经需要把它们翻译成它自己的二进制数字系统了,再一次,就像其他的数字系统一样,它不能代表所有的数字。而所有这些都只是做浮点计算,这是有误差的,他们甚至可以加起来,失去精度超出你需要的显示精度。
在数学中,0.9 “表示小数点” 后的连续9的重复小数点,因此1.0和0.9 *“表示完全相同的数。
现在,对于十进制数字系统字符串表示的JavaScript,很容易创建类似的印象:

> (22.95 * 4.1).toFixed(14).slice(0, -1)
'94.0949999999999'
> (22.95 * 4.1).toFixed(13)
'94.0950000000000'


toFixed()甚至可以添加重复的零(再次比较w/ WP)。
但这只是一个近似值,静态方法只舍入 “必要时”(自然是为了自己的操作,而不是为了 * 你的 * 数字和 * 你的 * 显示目的)。

> (22.95 * 4.1).toFixed(4).slice(0, -1)
'94.095'
> (22.95 * 4.1).toFixed(3)
'94.095'
> (22.95 * 4.1).toFixed(2)
'94.09' # not 94.10


由于数字存储在位字段中,因此它们只能存储为2的幂;则舍入误差在最后位置的单位中。
因此,在误差增加之前消除误差范围是很重要的。然后,您可以应用字符串格式,错误较少。
与十进制数字系统中显示数字所需的类似,您需要进行舍入。Number.prototype.toFixed()不会为你取整(只为它自己取整):

(Math.round(229.5 * 41) / 100).toFixed(2)
'94.10'


所有这一切自然都已经在现有的Q&A中概述了(参见。* Is floating point math broken? *)正好有您的问题(仅使用不同的值):

22.95 * 4.1 != 94.095


因此,当你执行浮点运算时,设计错误并始终编码(写入)数字,以便你以你需要的精度向你使用的方法传达值。
甚至有一个关于如何在JavaScript²中使用从phpjs项目(现在是locutus的一部分)复制的代码来执行PHP number_format()的现有Q&A,但是您表达数字的方式破解了它。这表明了忘记number_format()是舍入的是多么容易,因此处理的浮点精度错误与Number.prototype.toFixed()相比已经不同了。
² What is the JS equivalent to the PHP function number_format?

p4tfgftt

p4tfgftt2#

您在JavaScript端遇到了floating point error
22.95*4.1不是94.095(如您所料),而是(32位)浮点数学中的94.09499999999998,因此向下舍入为.09,而不是向上舍入为.10
在JavaScript中没有很好的方法来解决这个问题,但是可以使用ini_set('precision', 17)强制PHP像JavaScript一样工作。
参见问题Why can PHP calculate 0.1 + 0.2 when other languages fail?的答案:
PHP有一个precision配置值,用于设置浮点数中显示的有效位数。默认情况下为14,这就是0.1 + 0.2显示为0.3的原因。
但是,如果您这样做:

ini_set('precision', 17);
echo 0.1 + 0.2;

字符串
您将得到0.30000000000000004

相关问题