js基石之数据类型一:类型分类&区别
js基石之数据类型二:类型判断
js基石之数据类型三:类型转换
js基石之Number:本质
本文可以回答的问题
为什么10 === 10.0
为什么0.1 + 0.2 !== 0.3
什么叫Number的最大安全值?
什么叫Number的最大值?
js的数字精度问题具体是指什么?
js的数字精度问题如何解决?
数字的不同进制是如何转换的?
Number真正的含义:只有浮点数问题1
在 JavaScript 中Number 类型,内部表示为双精度浮点型
,即其他语言中的 double 类型,所以在 JavaScript 中实际上是没有整数类型的
,数值都是按浮点数来处理的,存储方法相同。
所以看下面的例子:
虽然我们虽然写了一个整数10,在存储时和10.0是没有区别的 两者是等价的。
存储形式: 一切问题的根源
Number按照IEEE 754标准 存储为64位
IEEE 754标准的浮点数表示中公式为
- 1个符号位(sign)
0为正 1为负
- 11个指数位(exponent) : 决定这个数的大小范围,也就是它的量级[-1022,1023] 问题4 解决
在IEEE 754标准中,指数位使用了一种称为"偏移二进制"(bias representation)的表示方法,可以表示正指数和负指数。。
在没有偏移的情况下,指数部分有11位,那么它能表示的范围是0到2047(2的11次方减1)。
但是,由于使用了偏移量, IEEE 754规定,中间数作为偏移量,对于11位而言就是1023作为偏移量,可以把1023看做是0,小于1023的表示负指数,大于1023的才是正指数,所以实际的指数范围是-1023到1024。
需要注意的是,指数部分的最大值(全1)和最小值(全0)在IEEE 754标准中被保留用于表示特殊值,如无穷大、无穷小和非数(NaN)。所以,实际可用的指数范围是-1022到1023。
- 52个尾数位(mantissa) : 决定这个数的精度,也就是它的具体值。问题3 解决
在IEEE 754标准的浮点数表示中,尾数部分的位数是52位。然而,这个尾数部分实际上表示的是一个小数,小数点后面的位数。在二进制中,这个小数的形式是1.xxxx,其中xxxx是尾数部分的52位。 这里的1.xxxx实际上是一个隐含的1,也就是说,我们实际上有53位的精度,而不仅仅是52位。这就是为什么尾数部分的最大值是2的53次方减1,而不是2的52次方减1。 这种表示方法被称为"规格化",它的目的是为了最大化浮点数的精度。通过隐含一个额外的1,我们可以得到更高的精度,同时也可以表示更大的数。
例子 问题2 解决
整数转换为二进制 除2取余直到 结果为0。
ini
let a = 12;
12/2 = 6 余 0
6/2 = 3 余 0
3/2 = 1 余 1
1/2 = 0 余 1
所以二进制表示为 1100
计算机按照IEEE 754标准存储:将二进制1100转换为标准表示法 1.100 * 2^3
符号位 正数 0
尾数位 为100 不满52位后面补0
指数位 因为小数点向左移动3位 所以指数为3 按照标准需要加上偏移量1023 所以指数部分为1026 转换为二进制10000000011 如果不满11位前面补0
所以最后的结果是
0 10000000010 1000000000000000000000000000000000000000000000000000
小数转换为二进制 乘2取整 直到小数部分为0
ini
let a = 0.2
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
....无限循环了
所以二进制为:0011 0011 0011 ...无限循环
计算机按照IEEE 754标准存储:将二进制 0011 0011 0011...无限循环 转换为标准表示法 1.1001100110011... * 2^3
符号位 正数 0
尾数位 100110011001100110011...直至填满52位
指数位 因为小数点向右移动3位 所以指数为-3 按照标准需要加上偏移量1023 所以为1020 转换为二进制01111111100 如果不满11位前面补0.
所以最后的结果是
0 01111111100 1001100110011001100110011001100110011001100110011001
第53位是1 已经达到最大位数了 后面依然有数且是1 需要四舍五入
所以最终结果
0 01111111100 1001100110011001100110011001100110011001100110011010
精度问题: 问题5 解决
大数运算:
JavaScript能够准确表示的最大整数是2的53次方减1。超过这个范围的整数,在JavaScript中无法准确表示。例如,如果你试图将2的53次方加1,结果仍然是2的53次方。
ini
2**53 + 1 === 2**53 // true
小数的精度:
由于JavaScript使用的是64位浮点数表示,所以小数的精度是有限的。如果一个小数的精度超过了这个范围,那么它在JavaScript中的表示就会有误差。
ini
0.1 + 0.2 === 0.3 // false
因为在转换为二进制的时候四舍五入有误差 所以最后的结果肯定也是不准确的。
特殊值的处理:
在JavaScript中,有一些特殊的值,如Infinity(无穷大)、-Infinity(无穷小)和NaN(非数)。这些值在进行运算时,可能会有一些非常规的行为,这也是JavaScript的数值精度问题的一部分。
总结下:
关于数值精度问题 并不是js独有的问题 只要是符合IEEE 754标准
的都会有这个问题。例如java同样也是不准确的。
csharp
public class Main {
public static void main(String[] args) {
double a = 0.1;
double b = 0.2;
double c = 0.3;
if (a + b == c) {
System.out.println("0.1 + 0.2 equals 0.3");
} else {
System.out.println("0.1 + 0.2 does not equal 0.3");
}
System.out.println(a + b);
}
}
// 结果
0.1 + 0.2 does not equal 0.3
0.30000000000000004
精度问题解决方案 问题6 解决
大数运算解决 BigInt
javascript
const bigNumber = BigInt(`${2**53 + 1 }`);
const bigNumber2 = BigInt(`${2**53 + 2 }`);
console.log(bigNumber + bigNumber2) // 18014398509481986n
浮点数运算解决
-
转换为整数计算
-
使用第三方库:
有许多第三方库可以帮助你进行精确的浮点数运算。例如,decimal.js、bignumber.js、mathjs等。这些库提供了一套完整的数学运算函数,可以用来进行精确的浮点数运算。
-
使用toFixed()或toPrecision()方法:
这两个方法可以将一个浮点数四舍五入到指定的小数位数或有效数字。这可以用来解决一些简单的精度问题。但是,需要注意的是,这两个方法返回的是一个字符串,而不是一个数字。
-
使用ES6中的Number.EPSILON:
Number.EPSILON是JavaScript中表示可接受的最小误差的常数。在比较两个浮点数是否相等时,可以使用Number.EPSILON来判断它们的差的绝对值是否小于Number.EPSILON。
javascript
function numbersCloseEnoughToEqual(n1, n2) {
return Math.abs(n1 - n2) < Number.EPSILON;
}
console.log(numbersCloseEnoughToEqual(0.1 + 0.2, 0.3)); // true
进制相互转换问题7 解决
十进制转N进制: toString
scss
(15).toString(2) // '1111'
(15.1).toString(2)
//'1111.0001100110011001100110011001100110011001100110011'
(15).toString(8) // '17'
(15).toString(16) // 'f'
N进制转十进制: parseInt
javascript
Number.parseInt('11',2) // 3
Number.parseInt('11',16) // 17
Number.parseInt('11',8) // 9