
Co-authored-by: Marco Borgeaud <89914223+marco-antognini-sonarsource@users.noreply.github.com>
99 lines
4.0 KiB
Plaintext
99 lines
4.0 KiB
Plaintext
== Why is this an issue?
|
|
|
|
Numbers in JavaScript are stored in https://en.wikipedia.org/wiki/Double-precision_floating-point_format[double-precision 64-bit binary format IEEE 754]. Like any other number encoding occupying a finite number of bits, it is unable to represent all numbers.
|
|
|
|
The values are stored using 64 bits in the following form:
|
|
|
|
* 1 bit for the sign (positive or negative)
|
|
* 11 bits for the exponent (2^n^). -1022 ≤ n ≤ 1023
|
|
* 52 bits for the significand (or mantissa)
|
|
|
|
// When images can be added, add image based on to this one
|
|
//image::IEEE_754_Double_Floating_Point_Format.svg.png[]
|
|
|
|
The actual value of the stored number will be `pass:n[(-1)^sign^ * (1 + significand) * 2 ^exponent^]`
|
|
|
|
Given this structure, there are limits in both *magnitude* and *precision*.
|
|
|
|
Due to the 52 bits used for the significand, any arithmetic in need of more precision than 2^-52^ (provided by `Number.EPSILON`) is subject to rounding.
|
|
|
|
In terms of magnitude, the largest number the 64 bits of the format can store is 2^1024^ - 1 (`Number.MAX_VALUE`).
|
|
|
|
However, because the 52 bits of the significand, only integers between -(2^53^ - 1) (`Number.MIN_SAFE_INTEGER`) and 2^53^ - 1 (`Number.MAX_SAFE_INTEGER`) can be represented exactly and be properly compared.
|
|
|
|
[source,javascript]
|
|
----
|
|
Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2; // true
|
|
----
|
|
|
|
JavaScript provides the `bigint` primitive to represent values which are too large to be represented by the number primitive. BigInts are created by appending `n` to the end of an integer literal, or by calling the `BigInt()` function (without the new operator), with an integer or a string.
|
|
|
|
|
|
[source,javascript]
|
|
----
|
|
const myBigInt = BigInt(Number.MAX_SAFE_INTEGER);
|
|
myBigInt + 1n === myBigInt + 2n; // false
|
|
----
|
|
|
|
|
|
== How to fix it
|
|
|
|
For large numbers, JavaScript provides the helper function `Number.isSafeInteger()` to test if a number is between the safe limits.
|
|
|
|
When you need to store a large number, use `BigInt`. `bigint` and `number` primitives can be compared between them as usual (e.g. `>`, `==`), but pay attention that arithmetic operations (`+` `pass:[*]` `-` `%` `++**++`) between both types raise an error unless they are converted to the same type. Use the `BigInt` and `Number` functions to convert between both types:
|
|
[source,javascript]
|
|
----
|
|
const myNumber = Number(myBigInt);
|
|
const myBigInt = BigInt(myNumber);
|
|
----
|
|
|
|
Be careful converting values back and forth, however, as the precision of a `bigint` value may be lost when it is coerced to a `number` value.
|
|
|
|
=== Code examples
|
|
==== Noncompliant code example
|
|
|
|
[source,javascript]
|
|
----
|
|
const foo = 2312123211345545367 // Noncompliant: will be stored as 2312123211345545000
|
|
const bar = BigInt(2312123211345545367); // Noncompliant: parameter is first parsed as an integer and thus rounded
|
|
----
|
|
==== Compliant solution
|
|
|
|
[source,javascript]
|
|
----
|
|
const foo = BigInt('2312123211345545367');
|
|
const bar = 2312123211345545367n;
|
|
----
|
|
|
|
When in need of more decimal precision, it is recommended to use a dedicated library to ensure that calculation errors are not introduced by rounding.
|
|
|
|
==== Noncompliant code example
|
|
[source,javascript]
|
|
----
|
|
const baz = 0.123456789123456789 // Noncompliant: will be stored as 0.12345678912345678
|
|
----
|
|
==== Compliant solution
|
|
[source,javascript]
|
|
----
|
|
// use a library like decimal.js for storing numbers containing many decimal digits
|
|
import { Decimal } from 'decimal.js';
|
|
const bar = new Decimal('0.123456789123456789');
|
|
----
|
|
|
|
//=== Pitfalls
|
|
|
|
//=== Going the extra mile
|
|
|
|
== Resources
|
|
|
|
=== Documentation
|
|
|
|
* MDN web docs - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#number_encoding[Number encoding]
|
|
* MDN web docs - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt[BigInt]
|
|
* Wikipedia - https://en.wikipedia.org/wiki/Double-precision_floating-point_format[Double-precision floating-point format]
|
|
* Wikipedia - https://en.wikipedia.org/wiki/IEEE_754[IEEE 754 Standard]
|
|
|
|
//=== Articles & blog posts
|
|
//=== Conference presentations
|
|
//=== Standards
|