当前位置: 首页 > ds >正文

JS toFixed的坑以及四舍五入实现方法

toFixed() 方法将数值转换为一个字符串,该字符串表示该数值与指定小数位数的最近近似值。当需要四舍五入时,它遵循IEEE 754标准中的四舍五入规则,这与一般的数学四舍五入规则有所不同。

语法:

    number.toFixed(digits)

digits:指定小数位数。

toFixed() 会将数字四舍五入到指定的小数位数,并返回一个字符串。如果小数位数不足,会用 0 填充。

toFixed() 遵循银行家舍入法,但有些时候和好像和银行家舍入法的结果不同,其实这是我们误会 toFixed() 了,主要是因为 浮点数在用二进制中存储时有精度误差导致 toFixed() 用银行家四舍五入时不准。

银行家舍入法

我国金融系统的大部分算法就是用四舍五入,国际上欧盟委员会对换汇时的舍入规定也是我们常见的四舍五入。

真正广泛采用银行家舍入法的,是需要更小误差的科学和计算机系统,因为多次舍入时银行家舍入法会比传统舍入法误差更小,因此银行家舍入法也叫统计学家舍入(statistician's rounding)、无偏舍入(unbiased rounding)。现在大部分编程软件的默认舍入都是银行家舍入法,比如 c/c++、javascript、php、go,英特尔处理器用的也是银行家舍入。

规则四舍六入五取偶:

•(1)被修约的数字小于5时,该数字舍去;

•(2)被修约的数字大于5时,则进位;

•(3)被修约的数字等于5时,要看5前面的数字,若是奇数则进位,若是偶数则将5舍掉,即修约后末尾数字都成为偶数;若5的后面还有不为“0”的任何数,则此时无论5的前面是奇数还是偶数,均应进位。

示例:

•9.8249=9.82,9.82671=9.83

•9.835=9.84,9.8351 =9.84

•9.825=9.82,9.82501=9.83

简单的说,就是:四舍六入五考虑,五后非空就进一,五后为空看奇偶,五前为偶应舍去,五前为奇要进一

测试银行家舍入法正常情况:

    // 5 后非 0 进位后舍去
    const err1 = 22.4451
    console.log('err1', err1.toFixed(2)); // 22.45


    // 5 后非 0 进位后舍去
    const err1 = 22.445002
    console.log('err1', err1.toFixed(2)); // 22.45


    // 5后无,5前一位为奇数 进位
    const err1 = 22.475
    console.log('err1', err1.toFixed(2)); // 22.48


    // 5后无,5前一位为偶数 舍去
    const err1 = 22.485
    console.log('err1', err1.toFixed(2)); // 22.48


测试异常情况:

    // 5后无,5前一位为奇数 进位
    const err1 = 22.415
    console.log('err1', err1.toFixed(2)); // 22.41


    // 5后无,5前一位为奇数 进位
    const err1 = 22.455
    console.log('err1', err1.toFixed(2)); // 22.45


    // 5后无,5前一位为偶数 舍去
    const err1 = 22.445
    console.log('err1', err1.toFixed(2)); // 22.45


    // 5后无,5前一位为偶数 舍去
    const err1 = 22.425
    console.log('err1', err1.toFixed(2)); // 22.43


    // 5后无,5前一位为偶数 舍去
    const err1 = 22.405
    console.log('err1', err1.toFixed(2)); // 22.41


    // 5后无,5前一位为奇数 进位
    const err1 = 0.015
    console.log('err1', err1.toFixed(2)); // 0.01

注意:这个挺吓人的,0.015 变成 0.01 慎用啊


原因分析:

    const err1 = 1.005
    console.log('err1', err1.toFixed(2)); // "1.00"(预期是 "1.01")

原因:浮点数在内存中的实际值可能略小于表面值。例如,`1.005` 实际存储为 `1.00499999999999989341858963598497211933135986328125`,导致四舍五入错误。

可以用下面方法查询浮点数在内存中存的值 getDecimalValue

    function getDecimalValue(floatNum) {// 这里不用 Decimal.toBinary, 因为toString 更准确const binaryString = '0b' + floatNum.toString(2)  // 将小数转为二进制// console.log(binaryString)let decimalValue = new Decimal(binaryString, 2).toString(); // 直接从二进制转换并创建Decimal对象console.log(decimalValue);return Number(decimalValue);}

 然后重新测一下上面的值发现结果都遵循银行家舍入了。

    const err1 = 22.415
    // 22.41499999999999914734871708787977695465087890625
    console.log(getDecimalValue(err1).toFixed(2)); // 22.41


    const err1 = 22.455
    // 22.4549999999999982946974341757595539093017578125
    console.log(getDecimalValue(err1).toFixed(2)); // 22.45


    const err1 = 22.445
    // 22.44500000000000028421709430404007434844970703125
    console.log(getDecimalValue(err1).toFixed(2)); // 22.45


    const err1 = 22.425
    // 22.425000000000000710542735760100185871124267578125
    console.log(getDecimalValue(err1).toFixed(2)); // 22.43


    const err1 = 22.425
    // 22.405000000000001136868377216160297393798828125
    console.log(getDecimalValue(err1).toFixed(2)); // 22.41


    const err1 = 0.015
    // 0.01499999999999999944488848768742172978818416595458984375
    console.log(getDecimalValue(err1).toFixed(2)); // 0.01

但是浮点数有数度问题怎么办呢,本人觉得可以考虑其它方法,自己写一个方法或是用下面方法

使用正常四舍五入

看到网上有很多这么写的,其实是不对的,不加 toFixed,这个结果是 1,没有 .00

    function roundUp(num, digits) {const factor = 10 ** digits;return (Math.round(num * factor) / factor).toFixed(digits);}const test = 1.005console.log(roundUp(test, 2));

上面结果还是 1.00 ,不是 1.01,还是有精度问题

因为 1.005 存储在内存中值是 1.00499999999999989341858963598497211933135986328125

乘以 100 后是 100.499999........ 四舍五入后还是 100 而不是 101,所以还是使用高精度库函数

也有用下面这种方法的,但是用 23.005 保留 2 位小数, 测试就不准了

    function roundUp(num, digits) {const factor = 10 ** digitsconst roundAdd1 = Math.round(num * factor * 10)const round = Math.round(roundAdd1 / 10)return (round / factor).toFixed(digits)}

Big.js

    function roundUp(num, precision = 0) {const bigNum = new Big(num).round(precision)return bigNum.toNumber().valueOf()}const test = 1.005console.log(new Big(test).toNumber().valueOf()); // 1.005console.log(roundUp(test, 2)); // 1.01

decimal.js

查看了源码,decimal.js 的 toFixed 默认用的是正常四舍五入

    const num3 = 1.005;console.log(new Decimal(num3).toFixed(2).toString());

如果需要配置银行家舍入法,可以进行设置

Decimal.set({ rounding: 5 })
或是
const Decimal9 = Decimal.clone({ rounding: 5 })
b = new Decimal9(1)

 rounding 配置表,默认是 1 (ROUND_DOWN)

PropertyValueDescription
ROUND_UP0Rounds away from zero
ROUND_DOWN1Rounds towards zero
ROUND_CEIL2Rounds towards Infinity
ROUND_FLOOR3Rounds towards -Infinity
ROUND_HALF_UP4Rounds towards nearest neighbour.
If equidistant, rounds away from zero
ROUND_HALF_DOWN5Rounds towards nearest neighbour.
If equidistant, rounds towards zero
ROUND_HALF_EVEN6Rounds towards nearest neighbour.
If equidistant, rounds towards even neighbour
ROUND_HALF_CEIL7Rounds towards nearest neighbour.
If equidistant, rounds towards Infinity
ROUND_HALF_FLOOR8Rounds towards nearest neighbour.
If equidistant, rounds towards -Infinity
EUCLID9Not a rounding mode, see modulo

但是小数点后面位数过多时,高精度库存储也会有问题,这个可能是 JavaScript 使用64位双精度,由0或1组成64位,有效数位数只有52位,有效数部分无法存储无限长度的二进制,后面的数据就会丢失,计算机只能存储一个近似的数字。

尽管 Decimal.js 旨在提供比原生 JavaScript 数字类型更高的精度,但它仍然受到底层浮点数系统的限制。

    const num3 = 1234567890.012345678;console.log(new Decimal(num3).toString()); // 1234567890.0123458 
http://www.xdnf.cn/news/2327.html

相关文章:

  • 可靠传输的守护者:揭开计算机网络传输层的奥秘
  • 【C++】14.容器适配器 | stack | queue | 仿函数 | priority_queue
  • 迷宫问题演示
  • Kafka + Kafka-UI
  • Python dotenv 使用指南:轻松管理项目环境变量
  • 【SSH 端口转发】通过SSH端口转发实现访问远程服务器的 tensorboard
  • 什么是函数依赖中的 **自反律(Reflexivity)**、**增广律(Augmentation)** 和 **传递律(Transitivity)?
  • Eclipse 插件开发 2
  • RASP技术在DevOps中的安全应用
  • Python-MCPServer开发
  • 产业观察:哈飞空客2025.4.26
  • 【MATLAB】基于RSSI原理的Wi-Fi定位程序,N个锚点(数量可自适应)、三维空间,轨迹使用UKF进行滤波,附完整代码(订阅专栏后可直接复制粘贴)
  • 100亿补贴不是终点:京东外卖在下一盘颠覆即时零售的大棋
  • w307MES生产制造执行系统设计与实现
  • SEO新手快速上手核心步骤
  • 【Android Compose】焦点管理
  • AWS中国区ICP备案全攻略:流程、注意事项与最佳实践
  • python 画折线统计图
  • 华为OD机试真题——二维伞的雨滴效应(2025A卷:200分)Java/python/JavaScript/C/C++/GO最佳实现
  • element ui el-col的高度不一致导致换行
  • AQS条件队列源码详细剖析
  • FPGA 100G UDP纯逻辑协议栈
  • 【C语言】柔性数组
  • 单片机-89C51部分:3、创建工程
  • 【MCP 应用】CherryStudio 配置和应用 MCP
  • node入门和npm
  • std::mutex底层实现原理
  • 使用命令关闭Redis服务端
  • 【Castle-X机器人】一、模块安装与调试:机器人底盘
  • 前端学习笔记(四)自定义组件控制自己的css