Cet article vous apporte une introduction détaillée aux nombres en JavaScript. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.
Avertissement : Les lecteurs doivent avoir une certaine compréhension du binaire
Pour les développeurs JavaScript, ils ont plus ou moins rencontré des phénomènes étranges dans le traitement des nombres par js, tels que :
> 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
Si vous voulez comprendre pourquoi ces phénomènes étranges se produisent, vous devez d'abord comprendre comment JavaScript code les nombres.
1. Comment JavaScript code-t-il les nombres ?
Les nombres en JavaScript, qu'ils soient entiers, décimaux, fractions, positifs ou négatifs, sont tous des nombres à virgule flottante. Ils sont tous stockés sur 8 octets (64 bits).
Un nombre (tel que 12
, 0.12
, -999
) occupe 8 octets (64 bits) en mémoire et est stocké comme suit :
0 - 51
: Partie fraction (52 bits)
52 - 62
: Partie exposant (11 bits)
63
: Bit de signe (1 bit : 0 signifie que le nombre est positif, 1 signifie que le nombre est négatif)
Le bit de signe est facile à comprendre, utilisé pour indiquer s'il s'agit d'un nombre positif ou négatif nombre, et il n'y a que 1 bit, Deux cas (0 pour les nombres positifs, 1 pour les nombres négatifs).
Les deux autres parties sont la partie fraction et la partie exposant, qui sont utilisées pour calculer la valeur absolue d'un nombre.
1.1 Formule de calcul de la valeur absolue
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
Explication :
Cette formule est une formule arithmétique binaire, et le le résultat est que abs
est représenté, la partie fraction est représentée par f
et la partie exposant est représentée par e
2 ^ (e - 1023)
représente 2
élevé à. la puissance de e - 1023
>
(48 0 sont omis au milieu) à f
(48 1 sont omis au milieu) 00...00
11...11
(e
) à 0
(00000000000
) 2047
11111111111
(1
, 1.00 * 2 ^ (1023 - 1023)
représente 48 0) f = 0000..., e = 1023
...
(2
, 1.00 * 2 ^ (1024 - 1023)
représente 48 zéros) f = 0000..., e = 1024
...
(9
, 1.01 * 2 ^ (1025 - 1023)
signifie 48 0s) f = 0100..., e = 1025
...
(0.5
, 1.00 * 2 ^ (1022 - 1023)
signifie 48 0s)f = 0000..., e = 1022
...
(0.625
, 1.01 * 2 ^ (1021 - 1023)
représente 48 zéros) f = 0100..., e = 1021
...
1.2.1
0 < e < 2047
Quand à 0 < e < 2047
(en omettant les 48 1 au milieu) f = 0, e = 1
f = 11...11, e = 2046
soit :
(Math.pow(2, -1022)
signifie approximativement égal à) ~= Math.pow(2, 1024) - 1
~=
Parmi eux,
La valeur de ~= Math.pow(2, 1024) - 1
est la valeur maximale qui peut être représentée. Number.MAX_VALUE
js
1.2.2
e = 0, f > 0
Quand (48 0 sont omis au milieu) à e = 0, f > 0
(48 1 sont omis dans le milieu) f = 00...01, e = 0
f = 11...11, e = 0
C'est-à-dire :
(Math.pow(2, -1074)
signifie approximativement égal à) ~= Math.pow(2, -1022)
~=
Parmi eux,
, et le plus petite valeur que Math.pow(2, -1074)
peut représenter Valeur numérique (valeur absolue). Number.MIN_VALUE
js
1.2.3
e = 0, f = 0
ne représente qu'une valeur et 0
. +0
-0
Mais en fonctionnement :
> +0 === -0 true
e = 2047, f > 0
ne représente qu'une seule valeur NaN
Mais en fonctionnement :
> NaN == NaN false > NaN === NaN false
e = 2047, f = 0
ne représente qu'une seule valeur ∞
En fonctionnement :
> Infinity === Infinity true > -Infinity === -Infinity true
. Number.MAX_VALUE
~= Math.pow(2, 1024) - 1
Mais cette valeur n'est pas sûre : les nombres de
ne sont pas continus, mais discrets. 1
比如: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
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!