〇、js 的数值计算存在结果不精确的情况

最近接触财务相关系统,页面上会有一些简单的计算,就发现其实是非常简单的计算,但 js 计算出来的结果却不是预期值,可能带上一大串 0 或 9,导致计算结果错误,本文来简单汇总下,以及如何处理来避免这个问题。

先看看都会有哪些不精确的情况。

// 【加减乘除 四种基础运算】
// 精度问题其实也不是全都会有,比如以下测试:
let num1 = 0.1;
let num2 = 0.1;
let result = num1 + num2;
console.log("0.1+0.1 = " + result);
// 0.1+0.1 = 0.2
// 0.1+0.2 = 0.30000000000000004
// 1.1*1.2 = 1.32
// 1.1*1.1 = 1.2100000000000002
// 其实四种基础运算都会出现类似情况,这里就简单列两类 // 【数值比较】
console.log("'0.1+0.2 = 0.3' 比较结果:", 0.1 + 0.2==0.3);
// '0.1+0.2 = 0.3' 比较结果: false

一、原因简介

因为计算机内部的信息都是由二进制方式表示的,即 0 和 1 组成的各种编码,但由于某些浮点数没办法用二进制准确的表示出来,也就带来了一系列精度问题。当然这也不是 js 独有的问题。

推荐一个文章吧,有兴趣可以深究下: https://zhuanlan.zhihu.com/p/33333351 ,讲的应该是很详细,但有些复杂博主看了一遍也不是太了解。

下面把大概的流程梳理下。

  • 将小数转成二进制数

小数部分乘 2 取整数部分,若小数不为 0 则继续乘 2,直至小数部分为 0。然后将取出的整数位正序排列。整数部分就除以 2。

0.1 * 2 = 0.2   // 取0
0.2 * 2 = 0.4 // 取0
0.4 * 2 = 0.8 // 取0
0.8 * 2 = 1.6 // 取1
0.6 * 2 = 1.2 // 取1
0.2 * 2 = 0.4 // 取0
......

  0.1 的二进制表示是:0.000110011......0011.....(0011无限循环)
  0.2 的二进制表示是:0.00110011......0011......(0011无限循环)

虽然是无限长度,但是还是得有一个标准的显示方式,那就是 IEEE 754 标准。

  • IEEE 754 标准方法表示数值

IEEE 754 标准是 IEEE 二进位浮点数算术标准(IEEE Standard for Floating-Point Arithmetic)的标准编号。IEEE 754 标准规定了计算机程序设计环境中的二进制和十进制的浮点数自述的交换、算术格式以及方法。

详细的标识法,就跳过了,直接来看结果。

js 只有一种数字类型 number,而 number 使用的是 IEEE 754 双精度浮点格式。最高位是一位符号位(0正 1负),后面的 11 位是指数,剩下的 52 位为尾数(有效数字)。

// js 中 0.1 的二进制存储格式为:
// 符号位用逗号分隔,指数位用分号分隔
0.1:0,01111111011;1001100110011001100110011001100110011001100110011010
0.2:0,01111111100;1001100110011001100110011001100110011001100110011010
  • 计算两个二进制数的和
// 二进制形式的结果
sum = 0.010011001100110011001100110011001100110011001100110100
// 最后再转成十进制
sum = 2^2 + 2^5 + 2^6 + ... + 2^52 = 0.30000000000000004440892098500626
// 近似后的结果:0.30000000000000004

详情可参考:https://zhuanlan.zhihu.com/p/33333351

二、解决方式

2.1 Math.Round() 函数,先乘后除 10 的 n 次方

根据需求的精度,先乘以 10 的 n 次方,通过 Math.Round() 函数取整后,再除以 10 的 n 次方。

function numToString(num){
let factor = Math.pow(10, 4); // 最多保留 4 位小数
// 通过乘以一个因子(例如 10 的 4 次方),然后四舍五入
// 最后,再除以相同的因子,可以实现对特定小数位数的精确控制
let roundedNum = Math.round(num * factor) / factor;
return roundedNum.toString();
}

关于 Math.Round() 四舍五入的规则,可以参考以下测试结果:

console.log("Math.round(4.2)     ", Math.round(4.2)  );
console.log("Math.round(4.5) ", Math.round(4.5) );
console.log("Math.round(4.7) ", Math.round(4.7) );
console.log("Math.round(-4.2) ", Math.round(-4.2) );
console.log("Math.round(-4.5) ", Math.round(-4.5) );
console.log("Math.round(-4.7) ", Math.round(-4.7) );
console.log("Math.round(1.5) ", Math.round(1.5) );
console.log("Math.round(2.5) ", Math.round(2.5) );
console.log("Math.round(3.5) ", Math.round(3.5) );
console.log("Math.round(-1.5) ", Math.round(-1.5) );
console.log("Math.round(-2.5) ", Math.round(-2.5) );
console.log("Math.round(-3.5) ", Math.round(-3.5) );

可以看出,正数的小数位为 5 时,进 1;负数小数位为 5 时,舍弃

2.2 toFixed() 方法,直接取小数点后固定位数

此方法就是直接指定要保留的几位小数,若小数位较少,就会以 0 补全

toFixed() 的四舍五入规则,并非严格的根据要保留小数位后边的小数来判断。

若作为判断的小数位为 5,且后边没有大于 0 的数,则舍入到最近的奇数;若 5 后边有非零的值,就直接进 1。

例如,4.55 保留 1 位小数,就是 4.5,但 4.5500001 的结果就是 4.6。

例如,-4.55 保留 1 位小数,就是 -4.5,但 -4.5500001 的结果就是 -4.6。

如下示例,供参考:

let num = 4.22;
console.log("num.toFixed(1):4.22) ", num.toFixed(1));
num = 4.55;
console.log("num.toFixed(1):4.55) ", num.toFixed(1));
num = 4.551;
console.log("num.toFixed(1):4.551) ", num.toFixed(1));
num = 4.65;
console.log("num.toFixed(1):4.65) ", num.toFixed(1));
num = 4.77;
console.log("num.toFixed(1):4.77) ", num.toFixed(1));
num = -4.22;
console.log("num.toFixed(1):-4.22) ", num.toFixed(1));
num = -4.55;
console.log("num.toFixed(1):-4.55) ", num.toFixed(1));
num = -4.551;
console.log("num.toFixed(1):-4.551) ", num.toFixed(1));
num = -4.65;
console.log("num.toFixed(1):-4.65) ", num.toFixed(1));
num = -4.77;
console.log("num.toFixed(1):-4.77) ", num.toFixed(1));
num = -4.77;
console.log("num.toFixed(1):-4.77) ", num.toFixed(4));

注意:toFixed() 的结果是字符串类型,若最终还需要 number 类型,就需要通过 Number() 函数进行转换。

2.3 通过正则表达式,多余位小数直接舍去

必须先将数字类型转换成字符串,再使用 match() 方法。

let num = 3.14959;
let numStr = num.toString(); // 必须为字符串
let fixedNumStr = numStr.match(/^-?\d+(\.\d{0,2})?/)[0]; // 两位小数的正则表达式
let fixedNum = parseFloat(fixedNumStr);
console.log(fixedNum); // 输出:3.14

js 数字计算的精度问题的更多相关文章

  1. JS数字计算精度误差的解决方法

    本篇文章主要是对javascript避免数字计算精度误差的方法进行了介绍,需要的朋友可以过来参考下,希望对大家有所帮助. 如果我问你 0.1 + 0.2 等于几?你可能会送我一个白眼,0.1 + 0. ...

  2. JS数字计算精度问题解决

    add(a, b) {//相加 var c, d, e; try { c = a.toString().split(".")[1].length; } catch (f) { c ...

  3. JS 浮点型计算的精度问题 推荐的js 库 推荐的类库 Numeral.js 和 accounting.js

    推荐的类库 Numeral.js 和 accounting.js 文章来自 http://www.css88.com/archives/7324#more-7324

  4. 关于js浮点数计算精度不准确问题的解决办法

    今天在计算商品价格的时候再次遇到js浮点数计算出现误差的问题,以前就一直碰到这个问题,都是简单的使用tofixed方法进行处理一下,这对于一个程序员来说是及其不严谨的.因此在网上收集了一些处理浮点数精 ...

  5. JavaScript数字计算精度丢失的问题和解决方案

    一.JS数字精度丢失的一些典型问题 1. 两个简单的浮点数相加:0.1 + 0.2 != 0.3 // true,下图是firebug的控制台截图: 看看java的计算结果:是不是让你很不能接受 再来 ...

  6. js数字位数太大导致参数精度丢失问题

    最近遇到个比较奇怪的问题,js函数里传参,传一个位数比较大,打印arguments可以看到传过来的参数已经改变. 然后查了一下,发现确实是js精度丢失造成的.我的解决方法是将数字型改成字符型传输,这样 ...

  7. js浮点数计算问题 + 金额大写转换

    一 js浮点数计算问题解决方案: 1.使用 NumberObject.toFixed(num) 方法 toFixed() 方法可把 Number 四舍五入为指定小数位数的数字. 2.较精度计算浮点数 ...

  8. js浮点数计算(加,减)

    最近工作中经常遇到需要处理浮点型计算的问题,开始一直都在用把浮点数先乘以10的对应小数的位数的次方化成整数再去开始计算. 例如100.01+100.02,可以化成(100.01*100+100.02* ...

  9. js中如何取精度

    js中如何取精度 一.总结 一句话总结:其实round()函数去经度会有误差,直接用num.toFixed(2)简单方便. toFixed()方法会按照指定的小数返回数值的字符串表示.var num ...

  10. js小数加减乘除时精度不准确

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/qq_33237207/article/d ...

随机推荐

  1. 使用 updateAppConfig 更新 Nuxt 应用配置

    title: 使用 updateAppConfig 更新 Nuxt 应用配置 date: 2024/8/27 updated: 2024/8/27 author: cmdragon excerpt: ...

  2. 【Docker学习系列】Docker学习2-docker设置阿里云镜像加速器

    在上一篇中,我们学会了在centos中安装docer.我们知道,镜像都是外网的,镜像一般都是比较大的,因为种种原因,我们知道,从外网下载比较慢的.所以,本文,凯哥就介绍怎么将docker的镜像拉取设置 ...

  3. manim边学边做--常用多边形

    多边形是常见的几何结构,它的形状看似千变万化,其实都可以由几种常用的多边形组合而成. 本篇介绍manim中提供的几个绘制常用多边形的模块. Triangle:等边三角形 Square:正方形 Rect ...

  4. CSS – Box Shadow & Text Shadow

    前言 之前在 CSS – W3Schools 学习笔记 (3) 介绍过这个功能, 但一直不熟练. 每次用都卡卡的, 估计是没有写一篇独立的笔记的缘故. 特此写一篇. Text Shadow 下面这个是 ...

  5. Vs Code, Visual Studio 2022, Angular and Live Server Running Through Https and IP Address

    前言 之前就写过 angular cli, vs code liveserver, vs 2019 iis express 10, vs code kestrel 使用 https + ip. 但写的 ...

  6. ASP.NET Core C# 反射 & 表达式树 (第三篇)

    前言 前一篇讲完了反射, 这一篇来讲一下和反射息息相关的表达式树. 首先搞清楚 Delegate, Action, Func, Anonymous Method, Lambda, Expression ...

  7. SQL Server – Schema

    前言 久仰 dbo 大名, 但是一直没有认真去看它有啥作用. 今天翻看了一下 SQL Server sample database: AdventureWorks2019, 发现它用了许多 Schem ...

  8. 柳婼 の PAT甲级题解

      1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 102 ...

  9. JavaScript习题之选择题

    console.log( (2==true)+1 )会弹出A trueB falseC 1D 2正确答案: C2 ==true为假,此时值为0 在JS中,"1555"+3的运行结果 ...

  10. JavaScript——基础语法

    书写语法    输出语句    变量    数据类型    运算符        == 与 === 区别:       ==:         1.判断类型是否一样,如果不一样,则进行类型转换     ...