JS小数运算精度丢失的问题

这篇具有很好参考价值的文章主要介绍了JS小数运算精度丢失的问题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

JS小数运算精度丢失的问题,数据分析,前端开发,JS,小数运算精度,算法

工作中会不会经常会碰到一些数据指标的计算,比如百分比转化,保留几位小数等,就会出现计算不准确,数据精度丢失的情况。通过这篇分享借助第三方库能够轻松解决数据精度丢失的问题。


一、场景复现

JS数字精度丢失的一些常见问题
// 加法 =====================
0.1 + 0.2 === 0.3 // false
0.1 + 0.2 = 0.30000000000000004
0.7 + 0.1 = 0.7999999999999999
0.2 + 0.4 = 0.6000000000000001

// 减法 =====================
1.5 - 1.2 = 0.30000000000000004
0.3 - 0.2 = 0.09999999999999998
 
// 乘法 =====================
19.9 * 100 = 1989.9999999999998
0.8 * 3 = 2.4000000000000004
35.41 * 100 = 3540.9999999999995

// 除法 =====================
0.3 / 0.1 = 2.9999999999999996
0.69 / 10 = 0.06899999999999999

为什么0.1 + 0.2 === 0.3是false呢?

先看下面这个比喻

比如一个数 1÷3=0.33333333......

3会一直无限循环,数学可以表示,但是计算机要存储,方便下次取出来再使用,但0.333333...... 这个数无限循环,再大的内存它也存不下,所以不能存储一个相对于数学来说的值,只能存储一个近似值,当计算机存储后再取出时就会出现精度丢失问题。

再看js里保留小数位tofixed()对于小数最后一位为5时进位不正确的问题
1.35.toFixed(1) // 1.4 正确
1.335.toFixed(2) // 1.33  错误
1.3335.toFixed(3) // 1.333 错误
1.33335.toFixed(4) // 1.3334 正确
1.333335.toFixed(5)  // 1.33333 错误
1.3333335.toFixed(6) // 1.333333 错误

可以看到,小数点位数为2,5时四舍五入是正确的,其它是错误。

根本原因还是计算机里浮点数精度丢失的问题

如:1.005.toFixed(2) 返回的是 1.00 而不是 1.01。

原因: 1.005 实际对应的数字是 1.00499999999999989,在四舍五入时全部被舍去。

1.005.toPrecision(21) //1.00499999999999989342

二、浮点数

“浮点数”是一种表示数字的标准,整数也可以用浮点数的格式来存储,我们也可以理解成,浮点数就是小数,

在JavaScript中,现在主流的数值类型是Number,而Number采用的是IEEE754规范中64位双精度浮点数编码,

这样的存储结构优点是可以归一化处理整数和小数,节省存储空间。

对于一个整数,可以很轻易转化成十进制或者二进制。但是对于一个浮点数来说,因为小数点的存在,小数点的位置不是固定的。解决思路就是使用科学计数法,这样小数点位置就固定了。

而计算机只能用二进制(0或1)表示,二进制转换为科学记数法的公式如下:

其中,a的值为0或者1,e为小数点移动的位置。

举个例子:

27.0转化成二进制为11011.0 ,科学计数法表示为:

JS小数运算精度丢失的问题,数据分析,前端开发,JS,小数运算精度,算法

其中,a的值为0或者1,e为小数点移动的位置。

举个例子:

27.0转化成二进制为11011.0 ,科学计数法表示为:

JS小数运算精度丢失的问题,数据分析,前端开发,JS,小数运算精度,算法

前面讲到,javaScript存储方式是双精度浮点数,其长度为8个字节,即64位比特,

64位比特又可分为三个部分:

符号位S:第 1 位是正负数符号位(sign),0代表正数,1代表负数;
指数位E:中间的 11 位存储指数(exponent),用来表示次方数,可以为正负数。在双精度浮点数中,指数的固定偏移量为1023;
尾数位M:最后的 52 位是尾数(mantissa),超出的部分自动进一舍零;
如下图所示:

JS小数运算精度丢失的问题,数据分析,前端开发,JS,小数运算精度,算法

举个例子:

27.5 转换为二进制11011.1

11011.1转换为科学记数法 [公式]

符号位为1(正数),指数位为4+,1023+4,即1027

因为它是十进制的需要转换为二进制,即 10000000011,小数部分为10111,补够52位即: 1011 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000`

所以27.5存储为计算机的二进制标准形式(符号位+指数位+小数部分 (阶数)),既下面所示:

0+10000000011+011 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000`

二、问题分析

再回到问题上

0.1 + 0.2 === 0.3 // false
0.1 + 0.2 = 0.30000000000000004

通过上面的学习,我们知道,在javascript语言中,0.1 和 0.2 都需要先将十进制转化成二进制后再进行运算。

// 0.1 和 0.2 都转化成二进制后再进行运算
0.00011001100110011001100110011001100110011001100110011010 +
0.0011001100110011001100110011001100110011001100110011010 =
0.0100110011001100110011001100110011001100110011001100111

// 转成十进制正好是 0.30000000000000004

所以输出false

再来一个问题,那么为什么x=0.1得到0.1?

主要是存储二进制时小数点的偏移量最大为52位,最多可以表达的位数是2^53=9007199254740992,对应科学计数尾数是 9.007199254740992,这也是 JS 最多能表示的精度。

它的长度是 16,所以可以使用 toPrecision(16) 来做精度运算,超过的精度会自动做凑整处理。

.10000000000000000555.toPrecision(16)
// 返回 0.1000000000000000,去掉末尾的零后正好为 0.1

但看到的 0.1 实际上并不是 0.1。不信你可用更高的精度试试:

0.1.toPrecision(21) = 0.100000000000000005551

小结

计算机存储双精度浮点数需要先把十进制数转换为二进制的科学记数法的形式,然后计算机以自己的规则{符号位+(指数位+指数偏移量的二进制)+小数部分}存储二进制的科学记数法。

因为存储时有位数限制(64位),并且某些十进制的浮点数在转换为二进制数时会出现无限循环,会造成二进制的舍入操作(0舍1入),当再转换为十进制时就造成了计算误差。

三、解决方案

理论上用有限的空间来存储无限的小数是不可能保证精确的,但我们可以处理一下得到我们期望的结果。

当你拿到 1.4000000000000001 这样的数据要展示时,建议使用 toPrecision 凑整并 parseFloat 转成数字后再显示,如下:

parseFloat(1.4000000000000001.toPrecision(12)) === 1.4  // True

封装成方法就是:

function strip(num, precision = 12) {
  return +parseFloat(num.toPrecision(precision));
}

对于运算类操作,如 +-*/,就不能使用 toPrecision 了。正确的做法是把小数转成整数后再运算(先扩大再缩小法)。

以加法为例:

/**
 * 精确加法
 */
function add(num1, num2) {
  const num1Digits = (num1.toString().split('.')[1] || '').length;
  const num2Digits = (num2.toString().split('.')[1] || '').length;
  const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits));
  return (num1 * baseNum + num2 * baseNum) / baseNum;
}

以toFixed()为例

//先扩大再缩小法 
function toFixed(num, s) {
    var times = Math.pow(10, s)
    // 0.5 为了舍入
    var des = num * times + 0.5
    // 去除小数
    des = parseInt(des, 10) / times
    return des + ''
}
console.log(toFixed(1.333332, 5))

最后还可以使用第三方库,如Math.js、BigDecimal.js文章来源地址https://www.toymoban.com/news/detail-720442.html

参考文献

  • 数值-阮一峰
  • BigInt - JavaScript | MDN

到了这里,关于JS小数运算精度丢失的问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • js 小数相乘后,精度缺失问题,记录四舍五入,向下取整

    在做项目的时候,有一个计算金额的,结果发现计算的金额总是缺失0.01,发现相乘的时候,会失去精度,如图所示。被这整的吐血,由于计算逻辑由前端计算,所以传值后端总出错(尽量后端计算)。 还发现toFixed也会失去进度,简单来说原因:算法不一样,叫说“银行家算法

    2024年02月07日
    浏览(33)
  • Java中整数相除并保留两位小数的实现方式、保留两位小数:Java中整数相除的处理方法、解决Java整数相除后精度丢失的问题、Java数值计算:整数相除保留指定小数位数的技巧

    当在Java中需要进行整数相除并保留两位小数时,可以使用以下两种方式实现: 1. 使用浮点数类型和DecimalFormat进行格式化:  2. 使用BigDecimal类进行精确的数值计算和格式化输出: 在选择使用哪种方式时,需要根据具体的业务需求和数值计算的精度要求进行权衡和选择。如果

    2024年02月12日
    浏览(31)
  • js精度丢失的问题

    1.js精度丢失的常见问题,从常见的浮点型进行计算,到位数很长的munber类型进行计算都会造成精度丢失的问题, 首先我们看一个问题: 那么js为什么会出现精度丢失的问题: 计算机的二进制实现和位数限制有些数无法有限表示。就像一些无理数不能有限表示,如 圆周率 3.1

    2024年02月14日
    浏览(37)
  • SpringBoot解决前端js处理大数字丢失精度问题Long转String

    一、Jackson对Long类型的处理导致精度丢失的问题 表的某一个字段的类型是 BIGINT,对应的 Java 类的属性的类型就是 Long。当这个字段的值由后端返回给前端网页时,发现了精度丢失的问题。比如后端返回的值是 588085469986509185,到了前端是 588085469986509200,后面的几位数变成了

    2024年02月21日
    浏览(29)
  • 后端传long类型数据到前端精度丢失问题

    在 Spring Boot 中,将 long 类型传输到前端时,会发现该类型的值可能会出现精度丢失的问题。 这是因为在 JavaScript 中,数字类型默认会被转换为双精度浮点数,而双精度浮点数的精度有限,只能精确表示 2 的 53 次方以内(即 Number.MAX_SAFE_INTEGER,约为 9 x 10^15)的整数。对于超过

    2024年02月15日
    浏览(29)
  • 前端接收后端数据时,遇到精度丢失的问题

    之前项目开发过程中遇到过前端接收后端数据时,遇到精度丢失的问题。当时进行了问题记录,本篇博客针对于这个问题进行问题原因并进行多种方式解决这个问题。 前端接收后端返回的数据时出现精度丢失问题,通常是因为在数据传输过程中,数据类型被转换为了 JavaScr

    2024年02月02日
    浏览(34)
  • 如何解决前端传递数据给后端时精度丢失问题

    有时候我们在进行修改操作时,发现修改既不报错也不生效。我们进行排查后发现服务器端将数据返回给前端时没有出错,但是前端js将数据进行处理时却出错了,因为id是Long类型的,而js在处理后端返回给前端的Long类型数据时只能处理前16位,后3位进行了四舍五入操作,例

    2024年02月09日
    浏览(32)
  • Mysql Docker 容器重启后数据库数据丢失分析

    项目复用nacos的mysql数据库且msyql容器已存在,之前已新建好数据库并插入数据。本次需要更新数据库表结构和数据。重启myql导致数据库数据丢失。 另外,本次事故是昨天发生未及时记录,日志不够详细。 本次需要更新数据库表结构和数据,通过dbviewer操作失败报 异常,部分

    2024年02月01日
    浏览(38)
  • Es 通过javaApi上传数据Long类型丢失精度的问题一次性解决

    通过 updateRequest.docAsUpsert(true) true 表示无匹配_id是插入数据,false 表示无匹配_id会抛出异常

    2024年02月15日
    浏览(35)
  • MyBatis-Plus主键策略(雪花算法16位长度的整型id,解决默认雪花算法生成19位长度id导致JS精度丢失问题)

    js表达的最大整数2的53次方减1,精度丢失后面几位全是0! 如果内置支持不满足你的需求,可实现 IKeyGenerator 接口来进行扩展. 举个栗子 #方式一:使用配置类 #方式二:通过 MybatisPlusPropertiesCustomizer 自定义 #方式一: XML 配置 #方式二:注解配置 官方示例 官方id generator示例 htt

    2023年04月08日
    浏览(28)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包