Javascript的toFixed方法不会四舍五入。

87

我正在使用JavaScript绑定一些复选框,但是toFixed(2)没有进行四舍五入。有什么想法为什么它没有进行四舍五入?例如,如果数字是859.385,它只显示859.38而不是859.39

我还听说toFixed可能会因使用的浏览器不同而进行不同的舍入,是否有任何方法来解决这个问题,使我的JavaScript计算与PHP计算匹配?

var standardprice = parseFloat($('#hsprice_'+this.id.split('_')[1]).val());
var price =  parseFloat($('#hprice_'+this.id.split('_')[1]).val());
var discount =  parseFloat($('#hdiscount_'+this.id.split('_')[1]).val());
var deposit =  parseFloat($('#hdeposit_'+this.id.split('_')[1]).val());

var currSprice = parseFloat($('#hTotalSprice').val());
var currPrice = parseFloat($('#hTotalPrice').val());
var currDiscount = parseFloat($('#hTotalDiscount').val());
var currDeposit = parseFloat($('#hTotalDeposit').val());

currSprice += standardprice;
currPrice += price;
currDiscount += discount;
currDeposit += deposit;

$('#lblTotalSprice').text('$'+addCommas(currSprice.toFixed(2)));
$('#lblTotalPrice').text('$'+addCommas(currPrice.toFixed(2)));
$('#lblTotalDiscount').text('$'+addCommas(currDiscount.toFixed(2)));
$('#lblTotalDeposit').text('$'+addCommas(currDeposit.toFixed(2)));

$('#hTotalSprice').val(currSprice.toFixed(2));
$('#hTotalPrice').val(currPrice.toFixed(2));
$('#hTotalDiscount').val(currDiscount.toFixed(2));
$('#hTotalDeposit').val(currDeposit.toFixed(2));

1
由于0.5恰好处于0和1的中间位置,向上舍入只是一种约定俗成的方式,我想知道保证特定结果真正的重要性。另一方面,为了测试您的代码,您需要可预测的结果,而测试非常重要,这是一个很好的理由。 - David Winiecki
2
以下是关于为什么 .toFixed 四舍五入会看起来有些难以理解的提示:(0.1).toFixed(20)。(请注意,IE 的实现给出了“直观”的结果,而其他浏览器则给出了符合标准的值。) - Noyo
1
我在类似的问题上的回答在这里:https://dev59.com/_FXTa4cB1Zd3GeqP4buc#37751946 - Pawel
25个回答

46

我还没有找到toFixed10无法正确处理的数字。有其他人遇到过吗?

感谢blg和他的回答,让我知道了Mozilla的toFixed10()方法。

使用这个方法,我想出了这个简短的一行代码,可以涵盖所有在此提到的情况...

function toFixed( num, precision ) {
    return (+(Math.round(+(num + 'e' + precision)) + 'e' + -precision)).toFixed(precision);
}

10
数字 0.0000001 或更小的数字会导致 NaN(不是数字) 的结果。 - Greenlandi
5
不对,这仍然不准确,“toFixed(89.684449, 2)”应该是“89.69”,但实际输出为“89.68”。 - mrts
2
这个没有处理双重舍入。这个答案可以解决问题 https://dev59.com/xGkw5IYBdhLWcg3wQ4Zc#43998255 - Codler
18
@mrts toFixed(89.684449, 2) 正确地显示为 89.68,如果是 89.685<任意其他无关数字>,它将只显示为 69。 - ed'
6
toFixed(0.000000015, 8) 中得到 NaN。为了得到正确答案 0.00000002,我使用以下函数:function round(v,d){return parseFloat(Math.round(v.toFixed(d+1)+'e'+d)+'e-'+d)}; - Geograph
显示剩余4条评论

28

我制作了这个用于所有财务数据的最佳舍入函数。你可以在所有有问题的数字上进行测试。 JavaScript 允许某种程度的精度,因此我使用它来使几乎每个数字舍入为预期值。

function roundTo(n, digits) {
        if (digits === undefined) {
            digits = 0;
        }

        var multiplicator = Math.pow(10, digits);
        n = parseFloat((n * multiplicator).toFixed(11));
        return Math.round(n) / multiplicator;
    }

18
评论有误导性,roundTo(89.684449, 2) 不应该是89.69,而是合法的89.68。 - Ivan Skalauh

25

21
通常情况下,这将确保您获得可预测的四舍五入。 然而,正如此答案中提到的,JavaScript处理小数的方式并不能真正信任。 请在Chrome控制台中尝试35.855 * 100...
我知道,我也很震惊!
这意味着 (Math.round( 35.855 * 100 ) / 100).toFixed(2) == 35.85 而不是35.86。有关提示,请参见该答案...
- mattbilson
4
这是标准的IEEE浮点数,这并不意味着JavaScript在这方面是不可信的。浮点数只是并非真实数字(在数学意义上),但它们是完全可预测的。 - idmean
这不是完美的答案。如果你输入1.005,你会得到1.00,但它应该是1.01。 - foxiris
1
@foxiris - 是的,由于浮点数的奇妙性质,1.005 * 100 的结果是 100.49999999999999,这会使算法出现偏差。 - jfriend00

20
在JavaScript的toFixed函数中,浮点数5不属于整数的上半部分,如果你有类似这样的数字,给定的数字会被向下舍入:
859.385.toFixed(2) // 结果为 859.38
事实上,你可能会添加尾部的浮点数(除了零),就像这样:
859.3851.toFixed(2) // 结果为 859.39
因此,开发人员倾向于添加诸如0.00000000001之类的数字,以便正确地进行舍入,并且不会意外改变数字的值。
所以我想出了一个函数,根据你想要固定的浮点数的位数来添加这样的数字:
    // both parameters can be string or number
    function toFixed(number, decimals) {
        const x = Math.pow(10, Number(decimals) + 2);
        return (Number(number) + (1 / x)).toFixed(decimals)
    }
    toFixed(859.385, 2) //results in 859.39
    toFixed(859.3844, 2) //results in 859.38

3
我能找到的最佳答案!代码易于阅读,甚至可以处理双重舍入! - Codler
9
toFixed(859.3844, 2) 的结果是 859.39,但应该是 859.38 - ed'
toFixed(.004, 2); == .00 ... 不是 .01 - MLissCetrus
通常我们知道要舍入到哪个精度。假设我们想要 num.toFixed(2)。那么将 0.0001 加到 num 上就可以得到 toFixed 的正确答案。虽然一个通用函数也可以,但在我看来它似乎有点过度设计,除非所需数字的位数是一个变量 - 这是不太可能的情况。 - Betty Mock
1
这个想法不错,但是有一个错误。添加的分数太大了,因此即使是 0.0046 也会被四舍五入为 0.01。将中间行改为 var x = Math.pow(10, Number(decimals) + 2); 可以解决这个问题。 - devaga
1
这个想法不错,但是有一个错误。添加的分数太大了,因此即使是 0.0046 也会被四舍五入为 0.01。将中间行改为 var x = Math.pow(10, Number(decimals) + 2); 可以解决这个问题。 - undefined

17

toFixed从来没有舍入,也不是为此而设计的。

它只是将浮点数(指数、尾数)转换为固定小数位数的定点数值(整数、小数部分)(详见定义)。

在执行此操作时,由于舍入后的数字是最接近原始浮点表示的近似值,因此看起来好像它正在进行舍入。

遗憾的是,广泛认为toFixed可用于四舍五入... 事实并非如此,即使名称也表明与此无关。

如果您需要进行四舍五入以及toFixed补齐:

function round(num, precision) {
  var base = 10 ** precision;
  return (Math.round(num * base) / base).toFixed(precision);
}

有人给这个人一个饼干。这是舍入货币的正确方式。 - francojay
3
round(148.325, 2) gives '148.32' because 148.325 * 100 is 14832.499999999998 - DrMeers

10

除了35.855之外,另一个值可以尝试的是1.005。

我认为罗伯特·梅瑟勒的解决方案无法处理1.005。

这里的四舍五入例子 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round$revision/1383484#Decimal_rounding 将数字转换为指数表示法,并似乎获得了更好的结果。

我在这里创建了一个fiddle http://jsfiddle.net/cCX5y/2/,演示了上面的原生、罗伯特·梅瑟勒示例(称为toFixedB)和Mozilla文档中的示例(称为toFixed10)。

到目前为止,我还没有发现toFixed10算错的数字。还有其他人吗?


2
好的代码片段和链接。我添加了user2823670的一行代码:http://jsfiddle.net/cCX5y/3/ - Slashback
1
看起来这个答案也可以工作,而且不需要所有额外的代码。 - Hanna
1
它们都不够精确,toFixed10(89.684449, 2) 和其他应该是 89.69,但实际上是 89.68 - mrts
7
我认为你进行了双重舍入。(https://en.wikipedia.org/wiki/Rounding#Double_rounding)任何数值,如1.2345都将被舍入为1.23而不是1.24。 - theycallmethesloth

9
function roundup(num,dec){
    dec= dec || 0;
    var  s=String(num);
    if(num%1)s= s.replace(/5$/, '6');
    return Number((+s).toFixed(dec));
 }

 var n= 35.855
 roundup(n,2)

/* 返回值:(数字)35.86 */



2
请使用 35.855 进行尝试。 - T.J. Crowder
这是我见过的唯一可预测舍入的解决方案。 - Sean the Bean
唯一的问题是,如果小数位之一为5且要舍入的小数位数高于该5的位置,则它将无法工作,例如roundup(70.5,2)。这可以通过更改替换语句中的正则表达式来轻松改进,例如:s.replace(/(\d{n})5/, '$16'),其中n等于dec参数。当舍入到n个小数位时,我们只需要更改小数点后第n + 1个位置上的5,我是对的吗? - Mansiemans

9

toFixed()函数效果正确! 问题是,计算机无法将859.385表示为浮点数。它被扫描为最接近的可能值= 859.384999999999991。将此值四舍五入到2位小数是859.38而不是859.39。

这就是为什么许多编程语言(特别是用于商业的旧语言,例如COBOL)支持BCD数字(二进制编码的十进制),其中每个数字都被编码为4位(类似于使用A-F的十六进制数字)。

价格的通用解决方法:以美分/便士为单位计算,并打印NUMBER/100。

注意其他解决方案(此处提供的函数): 它们可能对某些数字有所帮助,但大多数情况下会失败,例如859.38499999。


我在此处放置了更多信息链接 查看 - JillAndMe

6
如果您想要获得一个数字作为输出,那么请考虑其他答案中的Math.round()技术。
但是,如果您想要将字符串作为输出呈现给人类,那么通常情况下n.toLocaleString()n.toFixed()更有帮助。
为什么?因为它还会在大数字的开头添加逗号或句点,这是人类用来阅读的。例如:
var n = 1859.385

n.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})

// Produces  1,859.39  in USA (locale en-US)
// Produces  1 859,39  in France (locale fr-FR)
// Produces  1.859,39  in Germany (locale de-DE)

规范说明当第一个参数传递undefined时,将使用用户自己的区域设置(由操作系统指定)。不幸的是,如链接文档所示,Mozilla 在这种情况下使用 en-US 区域设置,但未来可能会遵守规范。

它也适用于负数,这很好。 - Simcha

6
你可以使用Math.round() 方法来对数字进行四舍五入。如果你想将数字保留到特定的小数位,可以运用一些简单的数学知识:
var result=Math.round(original*100)/100

13
请注意,这个数据不可靠。例如,如果你使用 35.855 进行测试,结果会是 35.85 而非 35.86 - T.J. Crowder
请注意,这只是一个示例。在插入“100”时,请使用“Math.pow(10, decimalpoint)”进行四舍五入数字的浮点位数。 - InsOp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接