Maison  >  Article  >  interface Web  >  Introduction détaillée au nombre en JavaScript

Introduction détaillée au nombre en JavaScript

不言
不言avant
2018-12-31 10:02:005538parcourir

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 :

  1. 0 - 51 : Partie fraction (52 bits)

  2. 52 - 62 : Partie exposant (11 bits)

  3. 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 >

  • Parce que la partie fraction occupe 52 bits, la plage de valeurs de
  • va de

    (48 0 sont omis au milieu) à f (48 1 sont omis au milieu) 00...0011...11

  • Étant donné que la partie exposant occupe 11 chiffres, la plage de valeurs de
  • va de

    (e) à 0 (00000000000 ) 204711111111111

  • Il ressort de la formule ci-dessus :

  • est stocké dans :

    (1, 1.00 * 2 ^ (1023 - 1023) représente 48 0) f = 0000..., e = 1023...

  • méthode de stockage :

    (2, 1.00 * 2 ^ (1024 - 1023) représente 48 zéros) f = 0000..., e = 1024...

  • méthode de stockage :

    (9, 1.01 * 2 ^ (1025 - 1023) signifie 48 0s) f = 0100..., e = 1025...

  • méthode de stockage :

    (0.5, 1.00 * 2 ^ (1022 - 1023) signifie 48 0s)f = 0000..., e = 1022...

  • méthode de stockage :

    (0.625, 1.01 * 2 ^ (1021 - 1023) représente 48 zéros) f = 0100..., e = 1021...

  • 1.2 valeur absolue Plage de valeurs et limite

Comme le montre la formule ci-dessus :

1.2.1

0 < e < 2047Quand

, la plage de valeurs est :

à 0 < e < 2047 (en omettant les 48 1 au milieu) f = 0, e = 1f = 11...11, e = 2046 soit :

à

(Math.pow(2, -1022) signifie approximativement égal à) ~= Math.pow(2, 1024) - 1~=Parmi eux,

est

La valeur de ~= Math.pow(2, 1024) - 1 est la valeur maximale qui peut être représentée. Number.MAX_VALUEjs1.2.2

e = 0, f > 0Quand

, la plage de valeurs est :

(48 0 sont omis au milieu) à e = 0, f > 0 (48 1 sont omis dans le milieu) f = 00...01, e = 0f = 11...11, e = 0C'est-à-dire :

à

(Math.pow(2, -1074) signifie approximativement égal à) ~= Math.pow(2, -1022)~= Parmi eux,

est la valeur de

, et le plus petite valeur que Math.pow(2, -1074) peut représenter Valeur numérique (valeur absolue). Number.MIN_VALUEjs1.2.3

e = 0, f = 0 ne représente qu'une valeur

, mais avec un bit de signe, il y a donc

et 0. +0-0Mais en fonctionnement :

1.2.4
> +0 === -0
true

e = 2047, f > 0 ne représente qu'une seule valeur

.

NaNMais en fonctionnement :

1.2.5
> NaN == NaN
false

> NaN === NaN
false

e = 2047, f = 0 ne représente qu'une seule valeur

(l'infini, l'infini).

En fonctionnement :

1.3 La valeur maximale sûre de la valeur absolue
> Infinity === Infinity
true

> -Infinity === -Infinity
true

Comme le montre ce qui précède, la valeur maximale pouvant être stockée dans 8 octets est

la valeur, qui est

. Number.MAX_VALUE~= Math.pow(2, 1024) - 1Mais 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,也就是从 1Number.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),然后存储在内存中,这就是失真,即不安全。

1.4 小数的存储方式与计算

小数中,除了满足 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

1.5 小数最大保留位数

js 从内存中读取一个数时,最大保留 17 位有效数字。

> 0.010011001100110011001100110011001100110011001100110011
0.30000000000000000
0.3
> 0.010011001100110011001100110011001100110011001100110010
0.29999999999999993
> 0.010011001100110011001100110011001100110011001100110100
0.30000000000000004
> 0.0000010100011110101110000101000111101011100001010001111100
0.020000000000000004

2. Number 对象中的常量

2.1 Number.EPSILON

表示 1 与 Number 可表示的大于 1 的最小的浮点数之间的差值。

Math.pow(2, -52)

用于浮点数之间安全的比较大小。

2.2 Number.MAX_SAFE_INTEGER

绝对值的最大安全值。

Math.pow(2, 53) - 1

2.3 Number.MAX_VALUE

js 所能表示的最大数值(8 个字节能存储的最大数值)。

~= Math.pow(2, 1024) - 1

2.4 Number.MIN_SAFE_INTEGER

最小安全值(包括符号)。

-(Math.pow(2, 53) - 1)

2.5 Number.MIN_VALUE

js 所能表示的最小数值(绝对值)。

Math.pow(2, -1074)

2.6 Number.NEGATIVE_INFINITY

负无穷大。

-Infinity

2.7 Number.POSITIVE_INFINITY

正无穷大。

+Infinity

2.8 Number.NaN

非数字。

3. 寻找奇怪现象的原因

3.1 为什么 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

3.2 为什么 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!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer