number.toFixed和Math.round与保留小数

探讨JavaScript中toFixed方法的银行家舍入规则及其在实际应用中的局限性,提出一种结合Math.round与toFixed的方法,以解决小数精度问题。

如果你baidu/google过或者自己写过保留两位小数,那下面这代码一定不陌生

Math.round(number*100)/100

那你使用过Number.prototype.toFixed这个方法吗。老实说此前我一次没用过,我猜我以前看书的时候没注意它(反省img...)。

今天看书复习再次看到这个方法,感觉很方便的,一个方法搞定保留小数,岂不是美滋滋。
研究以后发现事情并没有那么简单。

根据网上的说法,toFixed使用的是银行家舍入规则。并非我们熟悉的四舍五入,所以并不适合用来保留小数。对于银行家舍入的解释引用自互联网:

银行家舍入:所谓银行家舍入法,其实质是一种四舍六入五取偶(又称四舍六入五留双)法。
简单来说就是:四舍六入五考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一。

规则不难理解,可不知道为什么我就是想去浏览器控制台试试....

说好的银行家舍入呢(手动滑稽)。

到这里我开始自己对问题的猜想,是不是以前toFiexed舍入规则是这种银行家舍入规则,而后来随着版本的变更,方法已经变为我们熟知的四舍五入了。现在到底能不能用,我决定试试

复制代码
function contrast(number,fractionDigits) {
    var times = Math.pow(10,fractionDigits);
    var roundNum = Math.round(number*times)/times;
    var toFixedNum = number.toFixed(fractionDigits);
    if(roundNum.toString() !== toFixedNum){
        console.log('-------------------------');
        console.count();
        console.log("number:"+number);
        console.log("fractionDigits:"+fractionDigits);
        console.log("roundNum:"+roundNum);
        console.log("toFixedNum:"+toFixedNum);
    }
}
var number,
    fractionDigits;
for (var i = 0; i < 1000; i++) {
    number = Math.floor(Math.random()*Math.pow(10,16))/Math.pow(10,16);
    //一开始是以toFiexed参数范围取的20位,后来发现精度只支持16位 
    fractionDigits = Math.floor(Math.random()*16);
    contrast(number,fractionDigits)
}
复制代码

实验的结果,有两种情况,一是值一样,保留的小数不同(末尾0舍去与否)

坏就坏在第二种情况

出问题的数,保留小数,都在15位,难道是支持的准确精度位数不够吗,并不是的,比如:

这图也可以证明,toFiexed并非什么银行家舍入规则,至少我现在使用的chrome 62.0.3202.75不是,我使用的6.10.3nodejs也不是

网上找不到答案,那就书上找呗。在犀牛书里3.1.4里找到了一个原因,二进制浮点数表示法:

此外在知乎看到一个相当典型的例子1.555+1

可见1.555,在js里,其实是一个非常接近真实的1.555,但小于它的一个近似值。

我们认为的那个该进一的5其实在计算机眼中是4999..由上面的结果,不难预见到下面的结果

由此可见真正的问题源于二进制浮点数表示法并不能精准表示十进制分数!

----------------------------------------------分割线----------------------------------------------

理论完了,下面说说应用。

虽然toFixed由于二进制浮点数表示法的精确问题,并不能成为可靠的保留小数方案。

但我注意到其对小数位数的保留比round实现的保留小数(未做补零处理前)位数准确,也就是实验中的第一种情况。

我想利用这一特点,来给round保留的小数补零!!

由于Math.round(number*times)/times正确处理过toFiexed可能出错的5(4999....),这时候再用toFiexed,就可以避免错误(因为现在是999....\000...)。而又可以利用toFiexed正确保留小数位数的特点来补零。

 

复制代码
function contrast(number,fractionDigits) { 
    var times = Math.pow(10, fractionDigits);
    var roundNum = Math.round(number * times) / times;
    var toFixedNum = number.toFixed(fractionDigits);
    var decimal = roundNum.toString().split(".");
    var realValue1 = roundNum.toString();//手动补0
    var realValue2 = roundNum.toFixed(fractionDigits);//toFixed补0
    if(decimal.length === 2 ){
        if (decimal[1].length < fractionDigits) { 
            realValue1 = decimal[0] + '.' + (decimal[1] + "0000000000000000").slice(0,fractionDigits); 
        }
    }else if(fractionDigits !== 0){
        realValue1 = decimal[0] + '.' + ("0000000000000000").slice(0,fractionDigits);
    }
    if (realValue1 !== realValue2) {
        console.log('-------------------------');
        console.count('错误数');
        console.log("number:" + number);
        console.log("fractionDigits:" + fractionDigits);
        console.log("roundNum:" + roundNum);
        console.log("realValue1:" + realValue1);
        console.log("realValue2:" + realValue2);
        console.log("toFixedNum:" + toFixedNum);
    }else{
        console.count('正确数');
    }
}
var number,
    fractionDigits;
for (var i = 0; i < 10000; i++) {
    number = Math.floor(Math.random()*Math.pow(10,20))/Math.pow(10,20);
    fractionDigits = Math.floor(Math.random()*16);
    contrast(number,fractionDigits)
}
复制代码

实验了10000*10次随机数随机保留小数,无错误。

理论和实际都无问题,以后我保留小数就准备这样用了!

function toFixed(number,fractionDigits){
    var times = Math.pow(10, fractionDigits);
    var roundNum = Math.round(number * times) / times;
    return roundNum.toFixed(fractionDigits);
}

 

实例:服役年限(当前日期减开始使用日期,保留1位小数)

  var sdate = new Date(beginUseDate.replace(/-/g, "/")); 
  var now = new Date(); 
  var days = now.getTime() - sdate.getTime(); 
     //round保留1位小数
  var day = Math.round((days / (1000 * 60 * 60 * 24 * 365))*10)/10; 
     //toFixed小数位补零
  return day.toFixed(1); 


<think>我们正在处理一个关于JavaScript中使用toFixed(4)来保留小数的问题。 根据引用资料,我们知道toFixed()方法可以将数字转换为字符串,并保留指定的小数数。 但是,需要注意几个问题: 1. toFixed()方法返回的是字符串,而不是数字。 2. toFixed()使用的舍入规则是银行家舍入法(四舍六入五取偶)。 3. 如果需要四舍五入,并且避免字符串类型的问题,可能需要其他方法。 用户的需求是保留小数,所以我们可以使用toFixed(4)来实现,但要注意上述问题。 步骤: 1. 确保操作的对象是一个数字(Number类型)。 2. 调用该数字的toFixed(4)方法,得到保留小数的字符串表示。 3. 如果后续需要数字类型,可以再将其转换回数字(但这样可能会丢失精度,或者末尾的0会被忽略),所以通常保留小数用于显示时用字符串即可。 示例代码: ```javascript let num = 1.234567; let fixedNumStr = num.toFixed(4); // 返回字符串 "1.2346" console.log(fixedNumStr); // 输出: 1.2346 // 注意:如果原始值不是数字,需要先转换为数字 let numStr = "1.234567"; let numVal = parseFloat(numStr); let fixedStr = numVal.toFixed(4); // "1.2346" // 如果需要数字类型,可以转换回来,但注意这可能会引起浮点数精度问题 let numAgain = parseFloat(fixedStr); console.log(numAgain); // 输出: 1.2346 但是注意,在JavaScript中,1.2346实际上可能存储为1.2346000000000001等,但显示时通常不会显示多余的小数。 ``` 但是,根据引用[3][5],toFixed使用银行家舍入规则,这可能导致在某些情况下常见的四舍五入不同。如果用户希望使用严格的四舍五入,可以考虑使用Math.round方法,但需要自己处理。 另一种方法(使用Math.round保留小数): ```javascript function roundToFourDecimalPlaces(num) { return Math.round(num * 10000) / 10000; } // 但是这种方法返回的是数字,并且不会自动补0,比如1.2会变成1.2而不是1.2000 // 如果希望输出固定四小数,可以再使用toFixed(4)来格式化,但这样又回到了字符串,并且会使用银行家舍入规则。 // 所以,如果用户需要严格的四舍五入(而不是银行家舍入),并且希望结果是字符串,可以这样: function toFixedCustom(num, decimals) { // 将数字转换为字符串,然后使用指数表示法来避免浮点数精度问题 // 参考:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed#using_tofixed // 但这里我们采用另一种方式:使用字符串操作(为了避免银行家舍入) // 实际上,银行家舍入在大多数情况下四舍五入相同,只有在特定情况下(0.00005)会不同。 // 根据引用[5],我们可以先放大,四舍五入,再缩小,然后使用toFixed来补0(但这里我们使用自定义补0) // 然而,由于银行家舍入规则,我们使用Math.round来替代 const factor = Math.pow(10, decimals); // 使用Math.round进行四舍五入 let rounded = Math.round(num * factor) / factor; // 然后使用toFixed(decimals)来确保小数数,因为Math.round可能会因为浮点数精度导致小数数不足 return rounded.toFixed(decimals); } // 使用示例: let num1 = 1.23444; console.log(toFixedCustom(num1, 4)); // 1.2344 let num2 = 1.23445; console.log(toFixedCustom(num2, 4)); // 1.2345 (注意:在银行家舍入中,1.23445可能因为5后是0且5前是偶数而舍去,但这里我们使用Math.round,所以会进) // 但是注意,Math.round是标准的四舍五入(即0.5就进),而银行家舍入在0.5时看前一奇偶。 // 所以,如果用户希望使用标准的四舍五入,那么上述函数可以满足。 ``` 然而,根据引用[5],还有一种方法,就是先乘以10^decimals,然后使用Math.round,再除以10^decimals,然后使用toFixed来确保数。但是,由于浮点数精度,可能还需要处理精度问题。 总结: 1. 如果用户不介意银行家舍入规则,直接使用toFixed(4)即可。 2. 如果用户需要标准的四舍五入(0.5就进),那么可以使用自定义函数(如上面的toFixedCustom)来避免银行家舍入。 根据引用资料,用户提到了保留小数的精度问题,同样适用于保留小数。 因此,回答用户的问题: 在JavaScript中,使用toFixed(4)可以保留小数,但要注意返回的是字符串,并且使用银行家舍入规则。如果希望使用标准的四舍五入规则,可以自定义函数。 另外,如果用户需要将结果作为数字使用,那么要注意,保留小数后的字符串再转换为数字可能会丢失末尾的零,并且浮点数精度问题可能导致小数数不精确。因此,建议在显示时使用,存储计算时使用原始数字。 根据引用[4],也可以使用Math.round方法,但需要自己处理小数补零。 所以,我们提供两种方式: 方式一(直接使用toFixed,银行家舍入): let result = number.toFixed(4); 方式二(自定义函数,使用四舍五入): function toFixedStandard(num, decimals) { // 使用指数表示法来避免大数问题?这里使用简单的乘除 const factor = Math.pow(10, decimals); // 使用Math.round进行四舍五入 let rounded = Math.round(num * factor) / factor; // 使用toFixed来确保小数数,因为Math.round可能不会得到足够的小数(例如1.2保留4小数应该是1.2000) return rounded.toFixed(decimals); } 但是注意,方式二中的Math.round也可能受到浮点数精度的影响,比如1.23445 * 10000 可能等于12344.499999...,这样Math.round会变成12344,结果就是1.2344,而预期是1.2345。所以更可靠的方式是处理浮点数精度,例如将数字转换为字符串再操作,但这比较复杂。 因此,如果对精度要求非常高,建议使用专门的库,如decimal.js。 对于大多数情况,使用toFixed(4)即可,银行家舍入在统计学上更准确,且IEEE 754标准一致。 最后,根据用户上一次的问题,用户可能是在处理均方误差(MSE)的结果,这是一个数值,需要保留小数。在机器学习或数据分析中,通常使用四舍五入即可,银行家舍入也是可接受的。 所以,我们推荐使用: let mse = 0.123456789; let mseFixed = mse.toFixed(4); // "0.1235" 注意:0.123456789的四小数保留,银行家舍入:第5是5,第6是6>0,所以进,第4是4(偶数),但后面有56789,所以实际上是看第5(5)后面的6789,所以应该进,变成0.1235。 因此,直接使用toFixed(4)即可。 代码示例: let mse = calculateMSE(); // 假设计算得到MSE let mseStr = mse.toFixed(4); // 保留小数,转换为字符串 console.log(mseStr); // 输出如 "0.1235" 如果用户需要将结果作为数字传递给其他地方,请注意类型转换: let mseNum = parseFloat(mseStr); // 转换回数字,但注意,如果原始值很大,可能会损失精度,不过四小数通常不会 但通常,在展示给用户时使用字符串,在计算时使用原始数字(未截断的)。 相关问题: 1. 为什么toFixed(4)返回的是字符串? 2. 如何避免toFixed()的银行家舍入规则? 3. 在JavaScript中,如何正确比较两个保留小数后的数字? 4. toFixed(4)在什么情况下会返回科学计数法? 5. 如何将保留小数后的字符串转换为数字而不丢失精度? 我们选择其中几个生成: §§相关问题§§ 1. 为什么toFixed(4)返回的是字符串而不是数字? 2. 如何避免toFixed()的银行家舍入规则,实现标准的四舍五入? 3. 在JavaScript中,如何正确比较两个保留小数后的数字? 4. toFixed(4)在什么情况下会返回科学计数法?如何避免?</think>### JavaScript 保留小数 `toFixed(4)` 使用指南 在 JavaScript 中,使用 `toFixed(4)` 可将数字保留小数,但需要注意以下关键点: #### 基础用法 ```javascript let num = 1.23456789; let result = num.toFixed(4); // 返回字符串 "1.2346" console.log(result); // 输出: 1.2346 console.log(typeof result); // 输出: string(注意:结果是字符串类型) ``` #### 关键注意事项 1. **类型转换** `toFixed()` 返回字符串类型,需显式转换为数字(可能导致精度丢失): ```javascript let num = 0.12345; let numString = num.toFixed(4); // "0.1235"(字符串) let numFloat = parseFloat(numString); // 0.1235(数字) ``` 2. **舍入规则(银行家舍入法)** 采用 **四舍六入五取偶** 规则[^3]: ```javascript console.log((1.23444).toFixed(4)); // "1.2344"(四舍) console.log((1.23446).toFixed(4)); // "1.2345"(六入) console.log((1.23445).toFixed(4)); // "1.2344"(5后为零,前偶数舍去) console.log((1.23435).toFixed(4)); // "1.2344"(5后为零,前奇数进) ``` 3. **精度问题解决方案** 需严格四舍五入时,使用 `Math.round()` 替代[^4][^5]: ```javascript function roundToFour(num) { return Math.round(num * 10000) / 10000; } console.log(roundToFour(1.23445)); // 1.2345(数字类型) ``` #### 完整示例 ```javascript // 输入数字 let mse = 0.123456789; // 假设是均方误差结果 // 方案1:直接使用toFixed(4)(返回字符串) let mseString = mse.toFixed(4); // "0.1235" // 方案2:严格四舍五入(返回数字) let mseRounded = Math.round(mse * 10000) / 10000; // 0.1235 // 转换回字符串并补零(如0.1 → "0.1000") let formatted = mseRounded.toFixed(4); // "0.1235" ``` #### 使用场景建议 - **显示数据**:直接使用 `toFixed(4)` 转为字符串输出 - **计算需求**:先用 `Math.round()` 处理数字,需要显示时再调用 `toFixed(4)` - **精度敏感场景**:使用第三方库(如 `decimal.js`)避免浮点误差 > **关键提醒**:银行家舍入法在统计学中更准确,但若需传统四舍五入,需自定义函数处理[^3][^5]。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值