一、背景


最近做 dashborad 图表时,涉及计算小数且四舍五入精确到 N 位。后发现 js 算出来的结果跟我预想的不一样,看来这里面并不简单……

二、JS 与 精度


1、精度处理

首先明确两点:

  • 1、小数才会涉及精度的概念
  • 2、小数的(存储和)运算涉及 JS 的精度处理

在现实中,我们运算小数,不会出现任何问题。但是 JS (编程语言)里,却不是这样。

2、精度丢失

例如,在 JS 里执行:

0.1 + 0.2
0.30000000000000004 0.3 - 0.1
0.19999999999999998 0.1 * 0.1
0.010000000000000002 0.3 / 0.1
2.9999999999999996

可以看出,JS 运算小数的结果,并不是我们预想的那样。这就是精度丢失的问题。

(1)问:精度丢失会引发什么问题?

答:

  • 1、让判断等于(===)的逻辑出错。比如让 0.1 + 0.2 === 0.3false
  • 2、让本来可以预想到的结果精度变的特别大,小数点后位数特别长。比如若要前端显示,会特别难看。
(2)问:为什么会出现精度丢失?

答:这跟浮点数在计算机内部(用二进制存储)的表示方法有关。

JS 采用 IEEE 754 标准的 64 位双精度浮点数表示法,这个标准是20世纪80年代以来最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用,也被很多语言如 java、python 采用。

这个标准,会让大部分的十进制小数都不能用二进制浮点数来精确表示(比如转成二进制就会变成无限小数)。所以一般情况下,你输入的十进制小数仅由实际存储在计算机中的近似的二进制浮点数表示

然而,许多语言在处理的时候,在一定误差范围内(通常极小)会将结果修正为正确的目标数字,而不是像 JS 一样将存在误差的真实结果转换成最接近的小数输出。

具体原理可以看《浮点数的二进制表示 —— 阮一峰》,这里不赘述了。

(3)问:怎么避免精度丢失?

方法一:中途变成整数来计算

比如我们要计算 0.1 + 0.2,就先把数字全部乘以 10 使之变成整数,再相加,最后把结果除以 10。

因为整数是不会出现精度丢失的问题。(况且整数根本就没有精度)

其实很多第三方的库,原理也是用的这个。

方法二:使用第三方库

  • Math.js
  • decimal.js
  • big.js
  • bignumber.js

方法三:使用 toFixed() 函数(推荐)

console.log(parseFloat((0.3 + 0.1).toFixed(1))) // 0.4

注意:toFixed() 最好跟 parseFloat() 搭配使用。因为 toFixed 返回的是字符串

问:toFixed() 为什么要返回字符串,而不是小数?【重点】

答:因为 JavaScript 的数据类型,关于数字的只有 number 类型(不像 C 语言 or 数据库等还分 int、float、double),而对于 number 类型来说, 会忽略前置0和小数点后的后置0(比如 001 是 1; 1.1000 是 1.1)。

在下面还会继续介绍 toFixed() 的关于舍入的特性。

三、JS 与 近似计算方法


在上面提到的:

  • 精度计算
  • 精度丢失

都会有可能让精度发生变化(即小数点后位数变化)。如果我们需要统一精度,那就需要用到近似(计算)方法

1、四舍五入

(1)规则

四舍五入是最常见的近似计算方法,具体规则顾名思义,不赘述了。

(2)Math.round()

给定数字的值四舍五入到最接近的整数

Math.Round(2.4) // 2
Math.Round(2.5) // 3
(3)_.round() —— lodash

给定数字的值四舍五入到最接近的(可以是小数)。

lodash 的这个方法,我看了源码,底层也是调用的 Math.round(),只是加了一些额外功能,比如第二个参数,可以指定四舍五入的精度。

const _ = require('lodash');
_.round(1.04, 1) //1
_.round(1.05, 1) //1.1
(4)四舍五入真的公平吗?【重点】

因为自己很小的时候就在学校学到了四舍五入,一直想当然的认为四舍五入是公平的,等到现在细想的时候,才发现,真的不公平

例如,想象一个场景,你的余额宝,每天会自动结算利息,但是可能(按照利息规则)算出来的值的小数有很多位,假设支付宝只支持到角,那么支付宝系统帮你记账的时候,肯定会给你近似计算,如果他用的是四舍五入的方法:

const _ = require('lodash');
console.log(_.round(1.01, 1)) //1 (我亏了0.01)
console.log(_.round(1.02, 1)) //1 (我亏了0.02)
console.log(_.round(1.03, 1)) //1 (我亏了0.03)
console.log(_.round(1.04, 1)) //1 (我亏了0.04)
console.log(_.round(1.05, 1)) //1.1 (我赚了了0.05)
console.log(_.round(1.06, 1)) //1.1 (我赚了0.04)
console.log(_.round(1.07, 1)) //1.1 (我赚了0.03)
console.log(_.round(1.08, 1)) //1.1 (我赚了0.02)
console.log(_.round(1.09, 1)) //1.1 (我赚了0.01)

首先,1 块钱整和 2 块钱整可以不用考虑,其次,如果假设 1.01 到 1.09 这 9 个数出现的概率一致。那么最后支付宝肯定要亏本,因为 1.05 划分到 1.1 是不公平的。

也可以画一个数轴来体现:

那么如何做到更公平的近似计算呢?可以用下面介绍的银行家舍入。

2、银行家舍入

国际通行的是 银行家舍入(Banker's rounding)算法 。

是 IEEE 规定的舍入标准。因此所有符合 IEEE 标准的语言都应该是采用这一规则的。

(1)规则

银行家舍入又称四舍六入五取偶(又称四舍六入五留双)法。

所以规则就是:四舍六入五考虑,五后非空就进一,五后为空看奇偶,五前为偶应舍去,五前为奇要进一

关键就是“五后为空看奇偶”,因为如果是舍入位是5,无论是舍还是入都不公平,那就交给它前一位的奇偶性来判断,因为奇偶性分布概率是公平的。

当然只能说银行家舍入算法比四舍五入算法更科学,而不能说它就是绝对正确,而四舍五入就是错误的,因为这些结果都是基于统计数据产生的,前提就是这些数据摇符合随机性分布的要求。

(2)使用

目前 JS 上原生不支持,如果想使用:

3、toFixed

toFixed() 部分符合银行家舍入的规则。

(1)四舍六入

符合

(2)五后非空就进一

符合

(3)五后为空看奇偶,五前为偶应舍去,五前为奇要进一

部分符合

//                           //toFixed结果  //银行家舍入结果
console.log(1.05.toFixed(1)) //1.1(+0.05) 1.0(-0.05)
console.log(1.15.toFixed(1)) //1.1(-0.05) 1.2(+0.05)
console.log(1.25.toFixed(1)) //1.3(+0.05) 1.2(-0.05)
console.log(1.35.toFixed(1)) //1.4(+0.05) 1.4(+0.05)
console.log(1.45.toFixed(1)) //1.4(-0.05) 1.4(-0.05)
console.log(1.55.toFixed(1)) //1.6(+0.05) 1.6(+0.05)
console.log(1.65.toFixed(1)) //1.6(-0.05) 1.6(-0.05)
console.log(1.75.toFixed(1)) //1.8(+0.05) 1.8(+0.05)
console.log(1.85.toFixed(1)) //1.9(+0.05) 1.8(-0.05)
console.log(1.95.toFixed(1)) //1.9(-0.05) 2.0(+0.05)
// //总计(+0.1) //总计(0)

可以看出 toFixed 肯定是不遵守四舍五入的,但是也跟银行家舍入算法有出入。(具体为什么是这样的计算方法,鄙人并不是弄清楚,待写)

4、其他 近似计算 函数

  • Math.ceil():向上舍入(取整)
  • Math.floor():向下舍入(取整)
  • 等等……

关于 JavaScript 的 精度丢失 与 近似舍入的更多相关文章

  1. JavaScript数字精度丢失问题总结

    本文分为三个部分 JS 数字精度丢失的一些典型问题 JS 数字精度丢失的原因 解决方案(一个对象+一个函数) 一.JS数字精度丢失的一些典型问题 1. 两个简单的浮点数相加 0.1 + 0.2 != ...

  2. JavaScript数字精度丢失的一些问题

    本文分为三个部分 JS 数字精度丢失的一些典型问题 JS 数字精度丢失的原因 解决方案(一个对象+一个函数) 一.JS数字精度丢失的一些典型问题 1. 两个简单的浮点数相加 1 0.1 + 0.2 ! ...

  3. [转载]JavaScript 中小数和大整数的精度丢失

    标题: JavaScript 中小数和大整数的精度丢失作者: Demon链接: http://demon.tw/copy-paste/javascript-precision.html版权: 本博客的 ...

  4. 关于JavaScript中计算精度丢失的问题

    摘要: 由于计算机是用二进制来存储和处理数字,不能精确表示浮点数,而JavaScript中没有相应的封装类来处理浮点数运算,直接计算会导致运算精度丢失. 为了避免产生精度差异,把需要计算的数字升级(乘 ...

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

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

  6. 关于Java中用Double型运算时精度丢失的问题

    注:转自 https://blog.csdn.net/bleach_kids/article/details/49129943 在使用Java,double 进行运算时,经常出现精度丢失的问题,总是在 ...

  7. JavaScript 中精度问题以及解决方案

    JavaScript 中的数字按照 IEEE 754 的标准,使用 64 位双精度浮点型来表示.其中符号位 S,指数位 E,尾数位M分别占了 1,11,52 位,并且在 ES5 规范 中指出了指数位E ...

  8. JavaScript数字精度上代码。

    /**不能超过 9007199254740992 * floatObj 包含加减乘除四个方法,能确保浮点数运算不丢失精度 * * 我们知道计算机编程语言里浮点数计算会存在精度丢失问题(或称舍入误差), ...

  9. float类型进行计算精度丢失的问题

    今天一个案子,用户反映数量差异明明是 2.0-1.8,显示的结果却为0.20000005,就自己写了段方法测试了一下:package test1;public class Test2 {/*** @p ...

随机推荐

  1. 简单说 用CSS做一个魔方旋转的效果

    说明 魔方大家应该是不会陌生的,这次我们来一起用CSS实现一个魔方旋转的特效,先来看看效果图! 解释 我们要做这样的效果,重点在于怎么把6张图片,摆放成魔方的样子,而把它们摆放成魔方的样子,重点在于用 ...

  2. java多线程之间的通信

    目的 如何让两个线程依次执行? 那如何让 两个线程按照指定方式有序交叉运行呢? 四个线程 A B C D,其中 D 要等到 A B C 全执行完毕后才执行,而且 A B C 是同步运行的 三个运动员各 ...

  3. LeetCode:两数之和、三数之和、四数之和

    LeetCode:两数之和.三数之和.四数之和 多数之和问题,利用哈希集合减少时间复杂度以及多指针收缩窗口的巧妙解法 No.1 两数之和 给定一个整数数组 nums 和一个目标值 target,请你在 ...

  4. linux命令行界面如何安装图形化界面

    linux命令行界面如何安装图形化界面 目录 问题描述 解决方案 安装包 测试是否安装成功 如何卸载图形化界面 遭遇问题 问题描述 当我们在安装Linux系统时,我们一开始可能安装的是非图形界面的系统 ...

  5. Java集合01——List 的几个实现类,了解一下?

    从本文起,我们将开始分享 Java 集合方面的知识,关注公众号「Java面典」了解更多 Java 知识点. List 是继承于 Collection 的接口,其实现类有 ArrayList,Linke ...

  6. MATLAB神经网络(7) RBF网络的回归——非线性函数回归的实现

    7.1 案例背景 7.1.1 RBF神经网络概述 径向基函数是多维空间插值的传统技术,RBF神经网络属于前向神经网络类型,网络的结构与多层前向网络类似,是一种三层的前向网络.第一层为输入层,由信号源结 ...

  7. Simulink仿真入门到精通(二) Simulink模块

    2.1 Simulink模块的组成要素 用户构建系统模型时无需直接面对成千上万行的代码,而是通过模块化图形界面以模块化的方式构建,能够使理解变得容易,让大脑减负.通过层次化模块分布将系统功能模块化,而 ...

  8. R|tableone 快速绘制文章“表一”-基线特征三线表

    首发于“生信补给站” :https://mp.weixin.qq.com/s/LJfgxbTqsp8egnQxEI0nJg 生物医学或其他研究论文中的“表一”多为基线特征的描述性统计.使用R单独进行统 ...

  9. HTML5 history-hash 随机选择彩票

    <!doctype html> <html> <head> <meta charset="utf-8"> <title> ...

  10. js 运动的应用 新浪微博

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...