js数字计算问题
为什么JS计算会有精度问题
const a = 0.1;
const b = 0.2;
console.log(a + b); //0.30000000000000004
JavaScript 使用64位双精度浮点数来表示所有数字。由三部分组成:
- 符号位 (1 bit) - 表示正负
- 指数位 (11 bits) - 表示数量级
- 尾数位 (52 bits) - 表示精度
一》二进制无法精确表示某些十进制小数
比如,十进制的0.1和0.2在二进制中是无限循环小数,由于尾数只有52位,必须进行舍入,导致精度丢失
二》浮点数运算经过两次转换
- 十进制转二进制(精度丢失)
- 二进制计算
- 二进制转十进制(精度丢失)
三》存储空间有限
-
64位浮点数能精确表示的整数范围是
-2^53 + 1
到2^53 - 1
(即-9007199254740991
到9007199254740991
) -
超出这个范围的整数也会出现精度问题
所以计算大数字会出现问题
const bigNum = 9007199254740992; // 2^53 console.log(bigNum === bigNum + 1); // true
浮点数的计算精度问题
1.转为整数再计算
const a = 0.1;
const b = 0.2;
console.log((a * 100 + b * 100) / 100); // 0.3
小数四舍五入精度问题:
toFixed()
能四舍五入保留指定位数的小数, 0-20 之间的整数
它返回的是字符串,而不是数字
它的取舍原则:
-
四舍六入五考虑:当被修约的数字小于5时,该数字舍去;大于5时,则进位;等于5时,要看5前面的数字,若是奇数则进位,若是偶数则将5舍掉,即修约后末尾数字都成为偶数;若5的后面还有不为"0"的任何数,则此时无论5的前面是奇数还是偶数,均应进位12。
-
五后非零就进一:如果被修约的数字5后面有非零数字,则进位2。
-
五后为零看奇偶:如果被修约的数字5后面是零,则看5前面的数字,如果是奇数则进位,如果是偶数则舍去
101.595.toFixed(2);// '101.59'
这里看上去是595,但是实际存储值的小数部分约为 101.59500000000002
,所以打印出的是101.59
Math.round()
四舍五入取整;函数在内部实现时,会先将传入的数字加上0.5,然后向下取整。如果是四舍五入保留两位那原来数字小数不止两位的话也可能会出现精度问题
Math.round(101.595 * 100) / 100 // 101.59
因为JS实际算出来为101.595 * 100=10159.499999996,加上0.5向下取整是10159,再除以100也没得到321201.60
2.利用第三方库
math.js
mathjs功能全面,体积较大
基本加减乘除语法:
math.add(a,b)
math.subtract(a,b)
math.multiply(a,b)
math.divide(a,b)
链式调用:
let result = math.chain(3)
.add(4)
.multiply(2)
.done();
console.log(result); // 14
可以计算表达式,也支持大数字的计算:
可利用math.js的BigNumber模式解决精度问题
import { create, all } from 'mathjs';
// 配置使用 BigNumber
const math = create(all, {
number: 'BigNumber',
precision: 64
});
// 计算示例
console.log(math.evaluate('0.1 + 0.2').toString()); // "0.3"
console.log(math.evaluate('1 / 3').toString()); // "0.33333333333333333333"
decimal.js
适合高精度小数计算
plus(): 加法
minus(): 减法
times(): 乘法
div(): 除法
mod(): 取模(余数)
abs(): 绝对值
sqrt(): 平方根
pow(): 幂运算
toFixed(): 四舍五入,控制小数位数
toPrecision(): 设置有效数字
big.js
decimal.js语法:和big.js基本一样
轻量级
const num1 = new Big(0.1);
const num2 = new Big(0.2); // 传入字符串形式的数字,避免精度丢失
console.log(num1.plus(num2).toString()); // 0.3
三者对比:
第三方库 | math.js | decimal.js | big.js |
---|---|---|---|
定位 | 全能数学库 | 高精度十进制计算 | 轻量级精确计算 |
大小 | 约 100KB+ | 约 32KB | 约6KB |
功能 | 极其广泛 | 财务/科学计算 | 基础算术运算 |
性能 | 较慢 | 适中 | 最快 |
精度处理 | 可配置 | 可配置(默认20位) | 固定(默认20位) |
数字类型 | 支持多种类型 | 仅Decimal | 仅Big |
大数字的计算精度问题
1.BigInt
ES6中新增的一种基本数据类型,结尾比普通数字多了个n
浮点数不能直接转换为BigInt,会报错
const bigNum = 9007199254740992; // 2^53
console.log(bigNum); //9007199254740992;
console.log(BigInt(bigNum)); //9007199254740992n
console.log(bigNum === bigNum + 1); // true
console.log(BigInt(bigNum) === BigInt(bigNum) + BigInt(1)); // false
console.log(typeof BigInt(bigNum)); //bigint
2.使用第三方库
decimal.js、big.js、math.js