深入理解浮点数精度

2024-05-26 23:37:37

浏览:63

评论:0

前言

什么是浮点数

因为资源的限制,数学中的小数无法直接在计算机中准确表示。为了更好地表示它,计算机科学家们发明了浮点数,这是对小数的近似表示。

浮点数(Floating Point Number)由符号位、尾数和指数部分组成。符号位表示数的正负号,指数部分表示数的幅度,尾部部分表示数的精度。(基数默认是2)。

JavaScript中浮点数的表示形式:

// s 表示符号位,0表示正数,1表示负数。
// m 表示尾数,是一个非负数,通常以二进制表示。
// b 表示基数,是一个常量,通常为2。
// e 表示指数,是一个整数,通常以十进制表示。

(-1)^s * m * b^e

什么是IEEE 754标准

IEEE 754标准是一种用于表示浮点数的国际标准。它定义了浮点数的格式和操作,并且在多个计算机体系结构中得到了广泛的采用。

IEEE 754标准定义了三种浮点数类型:单精度浮点数(Single-precision)、双精度浮点数(Double-precision)和扩展精度浮点数(Extended-precision)。这些浮点数类型的表示形式和精度取决于具体的体系结构和编程语言。

单精度浮点数

双精度浮点数

IEEE 754标准还定义了一些操作,如加法、减法、乘法、除法和指数计算等,以及一些特殊的数值,如无穷大和NaN(Not a Number)。

浮点数精度问题

什么是精度问题

JS采用的是64位双精度浮点数来存储数字,而某些小数又无法准确表示为二进制有限小数的形式,因此在涉及到小数计算时,就可能会出现精度丢失的问题。

精度问题案例

console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 == 0.3) // false

解析0.1

const a = 0.1;
// 转为二进制
const aDec = a.toString(2)
// => 0.000110011001100110011001100110011001100110011001100110011001100{后面循环1100}
// 0.1转为二进制时因为有无限循环,在获取尾数时,第53位需要判断是否为1来进行进位,存在精度丢失
// 因为第53位为1的缘故,最后得到的二进制会比真实的0.1要大
// => 1.1001100110011001100110011001100110011001100110011010 * 2^-4
// 转换回十进制后
// => 1.00000000000000005551115123126E-1
// => 0.100000000000000005551115123126 > 0.1

同理 0.2 也会进行一样的操作

const a = 0.2;
// 转为二进制
const aDec = a.toString(2)
// => 0.001100110011001100110011001100110011001100110011001101{后面循环0011}
// 0.2转为二进制时因为有无限循环,在获取尾数时,第53位需要判断是否为1来进行进位,存在精度丢失
// 因为第53位为1的缘故,最后得到的二进制会比真实的0.2要大
// => 1.100110011001100110011001100110011001100110011001101 * 2^-3
// 转换回十进制后
// => 2.00000000000000011102230246252E-1
// => 0.200000000000000011102230246252 > 0.2

0.1,0.2 两数相加

0.1 + 0.2
=== // 两个对应的二进制相加
0.00011001100110011001100110011001100110011001100110011010
+
0.00110011001100110011001100110011001100110011001100110100

JS中的浮点数运算

  • Number.prototype.toFixed()
const numObj = 12345.6789;

numObj.toFixed(); // '12346';四舍五入,没有小数部分
numObj.toFixed(1); // '12345.7';向上舍入
numObj.toFixed(6); // '12345.678900';用零补足位数

(2.34).toFixed(1); // '2.3'
(2.35).toFixed(1); // '2.4';向上舍入
(2.55).toFixed(1); // '2.5'
  • Number.prototype.toPrecision()
let numObj = 5.123456;

console.log(numObj.toPrecision()); // 输出 '5.123456'
console.log(numObj.toPrecision(5)); // 输出 '5.1235'
console.log(numObj.toPrecision(2)); // 输出 '5.1'
console.log(numObj.toPrecision(1)); // 输出 '5'

numObj = 0.000123;

console.log(numObj.toPrecision()); // 输出 '0.000123'
console.log(numObj.toPrecision(5)); // 输出 '0.00012300'
console.log(numObj.toPrecision(2)); // 输出 '0.00012'
console.log(numObj.toPrecision(1)); // 输出 '0.0001'

解决浮点数精度问题

使用三方数学库

为了解决浮点数精度,经常使用的三方数学库有Decimal.jsBig.js

实现一个简单数学库的方案

  • 转换成整数计算有很多问题(不推荐)
13.53*100 // 1353
16.65*100 // 1664.9999999999998
  • 转换成字符串后计算

  • 转换成数组后计算

总结

参考文章 参考文章