This article brings you a detailed introduction to number in JavaScript. It has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.
Statement: Readers are required to have a certain understanding of binary
For JavaScript developers, they have more or less encountered strange phenomena in js processing numbers, such as :
> 0.1 + 0.2 0.30000000000000004 > 0.1 + 1 - 1 0.10000000000000009 > 0.1 * 0.2 0.020000000000000004 > Math.pow(2, 53) 9007199254740992 > Math.pow(2, 53) + 1 9007199254740992 > Math.pow(2, 53) + 3 9007199254740996
If you want to figure out why these strange phenomena occur, you must first figure out how JavaScript encodes numbers.
1. How does JavaScript encode numbers
Numbers in JavaScript, whether they are integers, decimals, fractions, positive or negative numbers, are all floating point numbers. They are all stored in 8 bytes (64 bits).
A number (such as 12
, 0.12
, -999
) occupies 8 bytes (64 bits) in memory, storage method As follows:
0 - 51
: Fraction part (52 bits)
##52 - 62: Exponent part (11 bits)
63: Sign bit (1 bit: 0 means the number is positive, 1 means the number is negative)
1.1 Absolute value calculation formula
1: abs = 1.f * 2 ^ (e - 1023) 0 < e < 2047 2: abs = 0.f * 2 ^ (e - 1022) e = 0, f > 0 3: abs = 0 e = 0, f = 0 4: abs = NaN e = 2047, f > 0 5: abs = ∞ (infinity, 无穷大) e = 2047, f = 0
abs is represented, the fractional part is represented by
f, and the exponent part is represented by
e
2 ^ (e - 1023) represents the
e - 1023 power of
2
f The value range is
00...00 (48 0s are omitted in the middle) to
11...11 (48 1s are omitted in the middle)
e is
0(
00000000000) to
2047(
11111111111)
1 Storage method:
1.00 * 2 ^ (1023 - 1023) (
f = 0000..., e = 1023,
... represents 48 zeros)
2 storage method:
1.00 * 2 ^ (1024 - 1023) (
f = 0000..., e = 1024 ,
... represents 48 0s)
storage method: 1.01 * 2 ^ (1025 - 1023)
(f = 0100..., e = 1025
, ...
represents 48 zeros)
: 1.00 * 2 ^ (1022 - 1023)
(f = 0000..., e = 1022
, ...
Represents 48 0s)
storage method: 1.01 * 2 ^ (1021 - 1023)
(f = 0100 ..., e = 1021
, ...
represents 48 zeros)
1.2.1
0 < e < 2047
Whenf = 0, e = 1 to
f = 11...11, e = 2046 (48 1s are omitted in the middle)
That is:
Math.pow(2, -1022)
~= Math.pow(2, 1024) - 1 (
~= means approximately Equal to)
Among them,
~= Math.pow(2, 1024) - 1
Number.MAX_VALUE, so
js The maximum value that can be represented.
1.2.2
e = 0, f > 0
When
e = 0, f > 0f = 00...01, e = 0 (48 0s are omitted in the middle) to
f = 11...11, e = 0 (48 1s are omitted in the middle)
That is:
Math.pow(2, -1074)
~= Math.pow(2, -1022)(
~= means approximately equal to)
Among them,
Math.pow(2, -1074)
Number.MIN_VALUE, which
js can The minimum value represented (absolute value).
1.2.3
e = 0, f = 0
only represents a value
0 0 and
-0.
But in the operation:
> +0 === -0 true
e = 2047, f > 0
This only represents one value
NaNBut in the operation:
> NaN == NaN false > NaN === NaN false
e = 2047, f = 0
This only represents one value
∞In operation:
> Infinity === Infinity true > -Infinity === -Infinity true
~= Math.pow(2, 1024) - 1.
But this value is not safe: the numbers from
1
Number.MAX_VALUE are not continuous, but discrete.
比如:Number.MAX_VALUE - 1
, Number.MAX_VALUE - 2
等数值都无法用公式得出,就存储不了。
所以这里引出了最大安全值 Number.MAX_SAFE_INTEGER
,也就是从 1
到 Number.MAX_SAFE_INTEGER
中间的数字都是连续的,处在这个范围内的数值计算都是安全的。
当 f = 11...11, e = 1075
(中间省略 48 个 1)时,取得这个值 111...11
(中间省略 48 个 1),即 Math.pow(2, 53) - 1
。
大于 Number.MAX_SAFE_INTEGER:Math.pow(2, 53) - 1
的数值都是离散的。
比如:Math.pow(2, 53) + 1
, Math.pow(2, 53) + 3
不能用公式得出,无法存储在内存中。
所以才会有文章开头的现象:
> Math.pow(2, 53) 9007199254740992 > Math.pow(2, 53) + 1 9007199254740992 > Math.pow(2, 53) + 3 9007199254740996
因为 Math.pow(2, 53) + 1
不能用公式得出,就无法存储在内存中,所以只有取最靠近这个数的、能够用公式得出的其他数,Math.pow(2, 53)
,然后存储在内存中,这就是失真,即不安全。
小数中,除了满足 m / (2 ^ n)
(m, n
都是整数)的小数可以用完整的 2 进制表示之外,其他的都不能用完整的 2 进制表示,只能无限的逼近一个 2 进制小数。
(注:[2]
表示二进制,^
表示 N 次方)
0.5 = 1 / 2 = [2]0.1 0.875 = 7 / 8 = 1 / 2 + 1 / 4 + 1 / 8 = [2]0.111
# 0.3 的逼近 0.25 ([2]0.01) < 0.3 < 0.5 ([2]0.10) 0.296875 ([2]0.0100110) < 0.3 < 0.3046875 ([2]0.0100111) 0.2998046875 ([2]0.01001100110) < 0.3 < 0.30029296875 ([2]0.01001100111) ... 根据公式计算,直到把分数部分的 52 位填满,然后取最靠近的数 0.3 的存储方式:[2]0.010011001100110011001100110011001100110011001100110011 (f = 0011001100110011001100110011001100110011001100110011, e = 1021)
从上面可以看出,小数中大部分都只是近似值,只有少部分是真实值,所以只有这少部分的值(满足 m / (2 ^ n)
的小数)可以直接比较大小,其他的都不能直接比较。
> 0.5 + 0.125 === 0.625 true > 0.1 + 0.2 === 0.3 false
为了安全的比较两个小数,引入 Number.EPSILON [Math.pow(2, -52)]
来比较浮点数。
> Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON true
js
从内存中读取一个数时,最大保留 17
位有效数字。
> 0.010011001100110011001100110011001100110011001100110011 0.30000000000000000 0.3
> 0.010011001100110011001100110011001100110011001100110010 0.29999999999999993
> 0.010011001100110011001100110011001100110011001100110100 0.30000000000000004
> 0.0000010100011110101110000101000111101011100001010001111100 0.020000000000000004
表示 1 与 Number 可表示的大于 1 的最小的浮点数之间的差值。
Math.pow(2, -52)
用于浮点数之间安全的比较大小。
绝对值的最大安全值。
Math.pow(2, 53) - 1
js
所能表示的最大数值(8 个字节能存储的最大数值)。
~= Math.pow(2, 1024) - 1
最小安全值(包括符号)。
-(Math.pow(2, 53) - 1)
js
所能表示的最小数值(绝对值)。
Math.pow(2, -1074)
负无穷大。
-Infinity
正无穷大。
+Infinity
非数字。
0.1 + 0.2
结果是 0.30000000000000004
与 0.3
的逼近算法类似。
0.1 的存储方式:[2]0.00011001100110011001100110011001100110011001100110011010 (f = 1001100110011001100110011001100110011001100110011010, e = 1019) 0.2 的存储方式:[2]0.0011001100110011001100110011001100110011001100110011010 (f = 1001100110011001100110011001100110011001100110011010, e = 1020)
0.1 + 0.2: 0.0100110011001100110011001100110011001100110011001100111 (f = 00110011001100110011001100110011001100110011001100111, e = 1021)
但 f = 00110011001100110011001100110011001100110011001100111
有 53 位,超过了正常的 52 位,无法存储,所以取最近的数:
0.1 + 0.2: 0.010011001100110011001100110011001100110011001100110100 (f = 0011001100110011001100110011001100110011001100110100, e = 1021)
js
读取这个数字为 0.30000000000000004
Math.pow(2, 53) + 1
结果是 Math.pow(2, 53)
因为 Math.pow(2, 53) + 1
不能用公式得出,无法存储在内存中,所以只有取最靠近这个数的、能够用公式得出的其他数。
比这个数小的、最靠近的数:
Math.pow(2, 53) (f = 0000000000000000000000000000000000000000000000000000, e = 1076)
比这个数大的、最靠近的数:
Math.pow(2, 53) + 2 (f = 0000000000000000000000000000000000000000000000000001, e = 1076)
取第一个数:Math.pow(2, 53)
。
所以:
> Math.pow(2, 53) + 1 === Math.pow(2, 53) true
The above is the detailed content of Detailed introduction to number in JavaScript. For more information, please follow other related articles on the PHP Chinese website!