js数值精度

lv_DaDa

近期在项目中有出现大数值的订单号9148368244236619在调用接口时自动变成9148368244236620的情况,导致请求失误。本文特意总结了出现这种情况的原因,以及js精度相关的情况。

jquery[.data()]方法

在本次案例中,订单号是后端同步渲染到页面上的,

<div class="j_OrderNumber" data-ordernumber="9148368244236619"></div>

呈现在页面上的订单号数值没有问题9148368244236619 ,前端此时想获取到这个订单号:

var orderNumber = $('.j_OrderNumber).data('ordernumber'); // 9148368244236620

typeof(orderNumber) === 'number' // true

在这个取值赋值的过程中,超过安全值的大数值发生了变化,从9148368244236619 变成了91483682442366120 ,这里的问题主要出现在jquerydata方法。

超过安全值Math.pow(2, 53)-1 -- 9007199254740991 的number类型在赋值的过程中会发生精度丢失,而我们在使用jquery的data方法取得的自定义html属性data-ordernumber值会强制转换成number类型,导致精度丢失。

而使用js原生的方法取dom节点的属性时,获取到的值都是string类型,这样就不会出现number类型精度丢失的问题。

var orderNumber = $('.j_OrderNumber')[0].dataset.ordernumber // '9148368244236619'

typeof(orderNumber) === 'string' // true

所以我们在通过自定义属性取值number类型时,并且预期这些值会是类似订单号这种会超过安全阈值的数值时,不要使用jquery的data方法。

大数值的精度问题

能够被“安全”呈现的最大整数是Math.pow(2, 53) - 1,即9007199254740992 。在ES6中被定义为Number.MAX_SAFE_INTEGER

在开发环境,根据程序的特殊性,在有可能出现这种情况时我们应该杜绝掉超出安全阈值的大整数,并给出友好提示:

function isSafeInteger(num) {
    return typeof(num) === 'number' && num % 1 == 0 && Math.abs(num) <= Math.pow(2, 53) -1;
}

在一般电商业务中比较常见出现大数值的场景也就是订单号了,这类场景后端传值给我们的时候都强制包装成string类型就会解决大多数的精度丢失问题了。

至于为什么会只有2的53次方-1的整数是安全的,可以看阮神的关于数值的文章有详细介绍

小数的精度问题

经典的 0.1 + 0.2 === 0.3 // false 问题

0.1 + 0.2 === 0.30000000000000004

小数比较

对于这类数值比较问题,如果我们已经知道了目标比较值,即如果我们已经明确要与0.3进行比较,我们也可以不需要得到0.1+0.2的真实期望结果值(0.3),因为如果我们要得到0.3,还需要对0.1和0.2进行操作。常规解法:

(0.1 * 10 + 0.2 * 10) / 10 = 0.3

在「你不知道的javascript」一书中有提到一种判断方法,设置一个误差范围值,通常也称为“机器精度”,对于javascript来说,这个值通常为Math.pow(2, -53)

我们可以将需要比较的两个值进行相减,再与这个机器精度进行比较,如果在误差范围内,我们也视为两个值是相等的。

function numbersCloseEnoughToEqual(num1, num2) {
    return Math.abs(num1 - num2) < Math.pow(2, -53);
}

小数展示

对于电商业务来讲,小数经过四则运算后可能会出现失去精度的问题,但是作为展示来说我们都会调用toFixed()进行小数后几位的约定,调用了这个方法后小数失去精度的问题也就迎刃而解了,不可能出现0.30000000000000004 这样的数值。

所以在业务中有需要进行小数四则运算并会展示在页面中,调用toFixed()方法!

但是toFixed()也有失去精度的时候!

1.335.toFixed(2)
// "1.33"

解决办法

function toFixed(num, s) {
    var times = Math.pow(10, s)
    var des = num * times + 0.5
    des = parseInt(des, 10) / times
    return des + ''
}

小数的四则运算

参考

本文来自二口南洋,有什么需要讨论的欢迎找我。

阅读 7.1k

南洋前端
前端杂技
1.7k 声望
0 粉丝
0 条评论
推荐阅读
import、require、export、module.exports 混合使用详解
自从使用了 es6 的模块系统后,各种地方愉快地使用 import export default,但也会在老项目中看到使用commonjs规范的 require module.exports。甚至有时候也会常常看到两者互用的场景。使用没有问题,但其中的关...

lv_DaDa120阅读 27.7k评论 11

还在用定时器吗?借助 CSS 来监听事件
平时工作中很多场合都要用到定时器,比如延迟加载、定时查询等等,但定时器的控制有时候会有些许麻烦,比如鼠标移入停止、移出再重新开始。这次介绍几个借助 CSS 来更好的控制定时器的方法,一起了解一下吧,相信...

XboxYan29阅读 2k评论 2

封面图
如何优雅地中断 Promise?来试试 AbortController 吧!
欢迎大家来到 前端小课堂 的第五期,今天我们来聊一聊如何终止正在进行中的 Fetch 以及 Promise。文中会跟大家详细介绍这里面的两个关键知识点 AbortController 和 AbortSignal。对动手实践比较感兴趣的同学还可...

dreamapplehappy23阅读 2.9k评论 4

封面图
前端性能优化(图文并茂,通俗易懂)
默认情况下,我们静态导入的所有模块都会添加到初始捆绑包中。使用默认 ES2015 导入语法 导入的模块将静态导入。import module from 'module'

寒水寺一禅26阅读 2.8k评论 1

超强的苹果官网滚动文字特效实现
每年的苹果新产品发布,其官网都会配套更新相应的单页滚动产品介绍页。其中的动画特效都非常有意思,今年 iPhone 14 Pro 的介绍页不例外。最近,刚好有朋友问到,其对官网的一段文字特效特别感兴趣,看适用简单却...

chokcoco24阅读 1.6k

封面图
50天用vue3完成了50个web项目,我学到了什么?
通过本文的50个web示例你将学到:Vue3核心基础语法和进阶语法less核心基础语法和进阶语法scss核心基础语法和进阶语法1.Expanding Cards效果如图所示:源码在线示例学到了什么?JavascriptVue ref方法定义基本响应式...

夕水21阅读 2k

封面图
一个被忽略的前端细分领域
大家好,我卡颂。回想下你学新技术的主要途径是什么?看书?看技术文档?看博文?看视频?归纳起来,无外乎文字、视频两种形式。从纸媒时代到互联网时代,再到移动互联网时代,虽然信息的载体发生变化,但信息的...

卡颂18阅读 1.4k

封面图
1.7k 声望
115 粉丝
宣传栏