You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+11-41Lines changed: 11 additions & 41 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
# Ecma TC39 JavaScript Decimal proposal
2
2
3
-
The TC39 Decimal proposal aims to add functionality to JavaScript to represent base-10 decimal numbers.
3
+
The TC39 Decimal proposal aims to add exact decimal arithmetic to JavaScript, eliminating the rounding errors that occur with binary floating-point numbers.
4
4
5
5
The champions welcome your participation in discussing the design space in the issues linked above. **We are seeking input for your needs around JavaScript decimal in [this survey](https://forms.gle/A2YaTr3Tn1o3D7hdA).**
6
6
@@ -25,14 +25,13 @@ As currently defined in JavaScript, Numbers are 64-bit binary floating-point num
25
25
26
26
The goal of the Decimal proposal is to add support to the JavaScript standard library for decimal numbers in a way that provides good ergonomics and functionality. JS programmers should feel comfortable using decimal numbers, when those are appropriate. Being built-in to JavaScript means that we will get optimizable, well-maintained implementations that don’t require transmitting, storing, or parsing and jit-optimizing every additional JavaScript code.
27
27
28
-
### Primary use case: Representing human-readable decimal values such as money
28
+
### Primary use case: Exact decimal arithmetic for financial calculations
29
29
30
30
Many currencies tend to be expressed with decimal quantities. Although it’s possible to represent money as integer “cents” (multiply all quantities by 100), this approach runs into a couple of issues:
31
31
32
32
- There’s a persistent mismatch between the way humans think about money and the way it’s manipulated in the program, causing mental overhead for the programmer aware of the issue.
33
33
- Some programmers may not even be aware of this mismatch. This opens the door to rounding errors whose source is unknown. If calculations start to get more involved, the chance of error increases.
34
-
- Different currencies use different numbers of decimal positions which is easy to get confused; the hack of working with quantities that are implicitly multiplied by 100 may not work when working with multiple currencies. For instance, it’s not correct to assume that all currencies have two decimal places, or that the only exception is JPY (Japanese yen); making such assumptions will make it hard to internationalize code to new countries. For this reason, it’s ideal if the number of decimal places is part of the data type.
35
-
- In various contexts (e.g., presenting a quantity to the end user), the decimal point needs to be brought back in somehow. For example, `Intl.NumberFormat` only knows how to format JS Numbers, and can’t deal with an integer-and-exponent pair.
34
+
- Different currencies use different numbers of decimal positions which is easy to get confused; the hack of working with quantities that are implicitly multiplied by 100 may not work when working with multiple currencies. For instance, it's not correct to assume that all currencies have two decimal places, or that the only exception is JPY (Japanese yen); making such assumptions will make it hard to internationalize code to new countries.
36
35
- Sometimes, fractional cents need to be represented too (e.g., as precise prices that occur, for instance, in stock trading or currency conversion).
37
36
38
37
#### Sample code
@@ -72,30 +71,6 @@ let amountInEur = exchangeRateUsdToEur.multiply(amountInUsd);
72
71
console.log(amountInEur.round(2).toString());
73
72
```
74
73
75
-
Here's the same example, this time using `Decimal.Amount`
76
-
and `Intl.NumberFormat` to properly handle currency:
77
-
78
-
```js
79
-
let exchangeRateEurToUsd =newDecimal("1.09");
80
-
let amountInUsd =newDecimal("450.27");
81
-
let exchangeRateUsdToEur =newDecimal(1).divide(exchangeRateEurToUsd);
82
-
let amountInEur =exchangeRateUsdToEur.multiply(amountInUsd);
83
-
let opts = { style:"currency", currency:"EUR" };
84
-
let formatter =newIntl.NumberFormat("en-US", opts);
85
-
let amount =Decimal.Amount(amountInEur).with({ fractionDigits:2 });
86
-
console.log(formatter.format(amount));
87
-
```
88
-
89
-
##### Format decimals with Intl.NumberFormat
90
-
91
-
We propose a `Decimal.Amount` object to store a Decimal value together with precision information. This is especially useful in formatting Decimal values, especially in internationalization and localization contexts.
92
-
93
-
```js
94
-
let a =Decimal.Amount.from("1.90").with({ fractionDigits:4 });
95
-
constformatter=newIntl.NumberFormat("de-DE");
96
-
formatter.format(a); // "1,9000"
97
-
```
98
-
99
74
#### Why use JavaScript for this case?
100
75
101
76
Historically, JavaScript may not have been considered a language where exact decimal numbers are even exactly representable, with the understanding that doing calculations is bound to propagate any initial rounding errors when numbers were created. In some application architectures, JS only deals with a string representing a human-readable decimal quantity (e.g, `"1.25"`), and never does calculations or conversions. However, several trends push towards JS’s deeper involvement in with decimal quantities:
@@ -119,7 +94,6 @@ This use case implies the following goals:
119
94
- Avoid unintentional rounding that causes user-visible errors
120
95
- Basic mathematical functions such as addition, subtraction, multiplication, and division
121
96
- Sufficient precision for typical money and other human-readable quantities, including cryptocurrency (where many decimal digits are routinely needed)
122
-
- Conversion to a string in a locale-sensitive manner
123
97
- Sufficient ergonomics to enable correct usage
124
98
- Be implementable with adequate performance/memory usage for applications
125
99
- (Please file an issue to mention more requirements)
@@ -145,11 +119,9 @@ This use case implies the following goals:
145
119
Interaction with other systems brings the following requirements:
146
120
147
121
- Ability to round-trip decimal quantities from other systems
148
-
- Serialization and deserialization in standard decimal formats, e.g., IEEE 754’s multiple formats
122
+
- Serialization and deserialization in standard decimal formats, e.g., IEEE 754's multiple formats
149
123
- Precision sufficient for the applications on the other side
150
124
151
-
The `Decimal.Amount` class also helps with data exchange, in cases where one needs to preserve all digits—including any trailing zeroes—in a digit string coming over the wire. That is, the `Decimal.Amount` class contains more information that a mathematical value.
152
-
153
125
#### Sample code
154
126
155
127
##### Configure a database adapter to use JS-native decimals
@@ -241,8 +213,6 @@ Based on feedback from JS developers, engine implementors, and the members of th
241
213
242
214
We will use the **Decimal128** data model for JavaScript decimals. Decimal128 is not a new standard; it was added to the IEEE 754 floating-point arithmetic standard in 2008 (and is present in the 2019 edition of IEEE 754, which JS normatively depends upon). It represents the culmination of decades of research on decimal floating-point numbers. Values in the Decimal128 universe take up 128 bits. In this representation, up to 34 significant digits (that is, decimal digits) can be stored, with an exponent (power of ten) of +/- 6143.
243
215
244
-
In addition to proposing a new `Decimal` class, we propose a `Decimal.Amount` class for storing a Decimal number together with a precision. The `Decimal.Amount` class is important mainly for string formatting purposes, where one desires to have a notion of a number that “knows” how precise it is. We do not intend to support arithmetic on `Decimal.Amount` values, the thinking being that `Decimal` already supports arithmetic.
@@ -293,9 +263,7 @@ Decimal is based on IEEE 754-2019 Decimal128, which is a standard for base-10 de
293
263
- a single NaN value--distinct from the built-in `NaN` of JS. The difference between quiet and singaling NaNs will be collapsed into a single (quiet) Decimal NaN.
294
264
- positive and negative infinity will be available, though, as with `NaN`, they are distinct from JS's built-in `Infinity` and `-Infinity`.
295
265
296
-
Decimal canonicalizes when converting to strings and after performing arithmetic operations. This means that Decimals do not expose information about trailing zeroes. Thus, "1.20" is valid syntax, but there is no way to distinguish 1.20 from 1.2. This is an important omission from the capabilities defined by IEEE 754 Decimal128. The `Decimal.Amount` class can be used to store an exact decimal value together with precision (number of significant digits), which can be used to track all digits of a number, including any trailing zeroes (which Decimal canonicalizes away).
297
-
298
-
The `Decimal.Amount` class will not support arithmetic or comparisons. Such operations should be delegated to the underlying Decimal value wrapped by a `Decimal.Amount`.
266
+
Decimal canonicalizes when converting to strings and after performing arithmetic operations. This means that Decimals do not expose information about trailing zeroes. Thus, "1.20" is valid syntax, but there is no way to distinguish 1.20 from 1.2. This is an important omission from the capabilities defined by IEEE 754 Decimal128.
299
267
300
268
### Operator semantics
301
269
@@ -336,10 +304,8 @@ Decimal objects can be constructed from Numbers, Strings, and BigInts. Similarly
336
304
-`toFixed()` is similar to Number's `toFixed()`
337
305
-`toPrecison()` is similar to Number's `toPrecision()`
338
306
-`toExponential()` is similar to Number's `toExponential()`
339
-
-`Intl.NumberFormat.prototype.format` should transparently support Decimal ([#15](https://github.com/tc39/proposal-decimal/issues/15)), which will be handled via `Decimal.Amount` objects
340
-
-`Intl.PluralRules.prototype.select` should similarly support Decimal, in that it will support `Decimal.Amount` objects
341
-
342
-
In addition, the `Decimal.Amount` object will provide a `toString` method, which will render its underlying Decimal value according to its underlying precision.
307
+
-`Intl.NumberFormat.prototype.format` will support Decimal values directly ([#15](https://github.com/tc39/proposal-decimal/issues/15))
308
+
-`Intl.PluralRules.prototype.select` will similarly support Decimal values
343
309
344
310
## Past discussions in TC39 plenaries
345
311
@@ -385,6 +351,10 @@ In our discussions we have consistently emphasized the need for basic arithmetic
385
351
386
352
These can be more straightforwardly added in a v2 of Decimal. Based on developer feedback we have already received, we sense that there is relatively little need for these functions. But it is not unreasonable to expect that such feedback will arrive once a v1 of Decimal is widely used.
387
353
354
+
### Precision metadata for formatting
355
+
356
+
A future addition could be a type that pairs a Decimal value with precision metadata (e.g., number of fractional digits). This would be useful for formatting scenarios where trailing zeroes matter, such as displaying currency amounts. Such functionality may be addressed by the TC39 [Amount proposal](https://github.com/tc39/proposal-amount); see [proposal-amount#75](https://github.com/tc39/proposal-amount/issues/75) for discussion.
0 commit comments