Skip to content

Commit 2bdda1d

Browse files
committed
Add Prior Art section, strengthen motivation arguments
Address concerns about the Decimal proposal's motivation and adoption potential by adding a Prior Art sections.
1 parent a1df094 commit 2bdda1d

File tree

2 files changed

+192
-7
lines changed

2 files changed

+192
-7
lines changed

README.md

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,11 +201,55 @@ If Decimal becomes a part of standard JavaScript, it may be used in some built-i
201201

202202
More host API interactions are discussed in [#5](https://github.com/tc39/proposal-decimal/issues/5).
203203

204+
## Prior Art
205+
206+
Adding decimal arithmetic would not be an instance of JS breaking new ground. Many major programming languages have recognized that decimal arithmetic is fundamental enough to include in the standard library, if not as a primitive type.
207+
208+
### Decimal Support Across Languages
209+
210+
| Language | Type Name | Location | Year Added | Notes |
211+
|----------|-----------|----------|------------|-------|
212+
| Python | `decimal.Decimal` | Standard library | 2003 (Python 2.3) | Based on [General Decimal Arithmetic Specification](http://speleotrove.com/decimal/) |
213+
| Java | `java.math.BigDecimal` | Standard library | 1998 (JDK 1.1) | Arbitrary precision, widely used for financial calculations |
214+
| C# | `decimal` | Primitive type | 2000 (C# 1.0) | 128-bit decimal type, first-class language support |
215+
| Swift | `Decimal` (formerly `NSDecimalNumber`) | Foundation framework | 2016 (Swift 3.0) | Standard library type for financial calculations |
216+
| Ruby | `BigDecimal` | Standard library | 1999 (Ruby 1.6) | Arbitrary precision decimal arithmetic |
217+
218+
SQL also has `NUMERIC`/`DECIMAL` as a built-in type, predating many programming languages.
219+
220+
Many languages added decimal support 10+ years ago. The IEEE 754-2008 standard for Decimal128 is now 17 years old. (The 2019 edition of IEEE 754 also has decimal arithmetic.)
221+
Indeed, multiple numeric types are not unusual. Languages ship with integers, floats, *and* decimals. Python even includes both decimals and rationals. The existence of JS's `Number` and `BigInt` doesn't preclude `Decimal`. Moreover, the need for decimal types across many languages reflects a genuine, universal need rather than a niche requirement.
222+
223+
### Why JavaScript Lacks Decimal Support
224+
225+
JavaScript's origins as a lightweight browser scripting language meant it initially shipped with minimal numeric support (just IEEE 754 binary floats). However, JavaScript's role has fundamentally changed:
226+
227+
- **Then (1995)**: Simple form validation and DOM manipulation
228+
- **Now (2025)**: Full-stack applications, financial systems, e-commerce platforms, serverless backends, data processing pipelines
229+
230+
The language has evolved to meet modern needs (adding `BigInt`, `async`/`await`, modules, etc.), but decimal arithmetic remains a critical gap.
231+
232+
### The Cost of Not Having Decimal
233+
234+
Because JavaScript lacks native decimal support, developers have created numerous userland solutions:
235+
236+
- [**decimal.js**](https://www.npmjs.com/package/decimal.js): ~2M weekly npm downloads
237+
- [**bignumber.js**](https://www.npmjs.com/package/bignumber.js): ~800K weekly npm downloads
238+
- [**big.js**](https://www.npmjs.com/package/big.js): ~500K weekly npm downloads
239+
240+
There are also money-specific libraries such as [dinero.js](https://www.npmjs.com/package/dinero.js) with about 180K weekly NPM downloads.
241+
242+
Each implementation has slightly different semantics, requires coordination across libraries, adds to total bundle size, presumably performs worse than native implementations could, and creates interoperability challenges.
243+
244+
Compare this to BigInt: before native support, polyfills and userland big integer libraries had minimal adoption because the *need* was niche. Decimal libraries show massive adoption because the need is universal.
245+
246+
Thus, Decimal standardizes existing practice. Developers are already using decimal libraries (millions of downloads/week), converting numbers to "cents" (error-prone, confusing), using `Number` incorrectly (causing bugs). Decimal doesn't ask developers to adopt something new; it offers a better way to do what they're already struggling to do.
247+
204248
## Specification and standards
205249

206250
Based on feedback from JS developers, engine implementors, and the members of the TC39 committee, we have a concrete proposal. Please see the [spec text](https://github.com/tc39/proposal-decimal/blob/main/spec.emu) ([HTML version](https://github.com/tc39/proposal-decimal/blob/main/spec.emu)). are provided below. You’re encouraged to join the discussion by commenting on the issues linked below or [filing your own](https://github.com/tc39/proposal-decimal/issues/new).
207251

208-
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. It represents the culmination of decades of research, both theoretical and practical, 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.
252+
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.
209253

210254
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.
211255

docs/cookbook.md

Lines changed: 147 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,150 @@ const total = price.multiply(quantity);
2020
console.log(total.toString()); // => "59.97"
2121
```
2222

23+
## Prior Art: Decimal Arithmetic in Other Languages
24+
25+
The patterns demonstrated in this cookbook are not unique to JavaScript - they reflect well-established practices across programming languages. This section shows how other languages handle the same decimal arithmetic challenges, demonstrating that native decimal support is standard across the industry.
26+
27+
### Python
28+
29+
Python includes the `decimal` module in its standard library (since 2003):
30+
31+
```python
32+
from decimal import Decimal
33+
34+
# Financial calculations
35+
price = Decimal("19.99")
36+
quantity = Decimal("3")
37+
tax_rate = Decimal("0.0825")
38+
39+
subtotal = price * quantity
40+
tax = subtotal * tax_rate
41+
total = subtotal + tax
42+
43+
print(f"${total:.2f}") # => "$64.62"
44+
```
45+
46+
Python's `Decimal` is widely used in web frameworks like Django and Flask for handling monetary values. The Django ORM includes a `DecimalField` specifically for financial data.
47+
48+
### Java
49+
50+
Java's `BigDecimal` (in the standard library since 1998) is ubiquitous in enterprise applications:
51+
52+
```java
53+
import java.math.BigDecimal;
54+
import java.math.RoundingMode;
55+
56+
BigDecimal price = new BigDecimal("19.99");
57+
BigDecimal quantity = new BigDecimal("3");
58+
BigDecimal taxRate = new BigDecimal("0.0825");
59+
60+
BigDecimal subtotal = price.multiply(quantity);
61+
BigDecimal tax = subtotal.multiply(taxRate);
62+
BigDecimal total = subtotal.add(tax);
63+
64+
// Round to 2 decimal places
65+
total = total.setScale(2, RoundingMode.HALF_UP);
66+
System.out.println(total); // => "64.62"
67+
```
68+
69+
`BigDecimal` is the standard approach for financial applications in the Java ecosystem, including frameworks like Spring and Hibernate.
70+
71+
### C\#
72+
73+
C# includes `decimal` as a primitive type (since 2000), giving it first-class language support:
74+
75+
```csharp
76+
decimal price = 19.99m;
77+
decimal quantity = 3m;
78+
decimal taxRate = 0.0825m;
79+
80+
decimal subtotal = price * quantity;
81+
decimal tax = subtotal * taxRate;
82+
decimal total = subtotal + tax;
83+
84+
Console.WriteLine($"${total:F2}"); // => "$64.92"
85+
```
86+
87+
### Ruby
88+
89+
Ruby includes `BigDecimal` in its standard library:
90+
91+
```ruby
92+
require 'bigdecimal'
93+
94+
price = BigDecimal("19.99")
95+
quantity = BigDecimal("3")
96+
tax_rate = BigDecimal("0.0825")
97+
98+
subtotal = price * quantity
99+
tax = subtotal * tax_rate
100+
total = subtotal + tax
101+
102+
puts "$%.2f" % total # => "$64.92"
103+
```
104+
105+
Ruby on Rails uses [`BigDecimal`](https://ruby-doc.org/3.4.1/exts/json/BigDecimal.html) for handling database `decimal` columns by default, making it the standard approach for money in Rails applications.
106+
107+
### Swift
108+
109+
Swift's Foundation framework includes [`Decimal`](https://developer.apple.com/documentation/foundation/decimal) (formerly [`NSDecimalNumber`](https://developer.apple.com/documentation/foundation/nsdecimalnumber)):
110+
111+
```swift
112+
import Foundation
113+
114+
let price = Decimal(string: "19.99")!
115+
let quantity = Decimal(string: "3")!
116+
let taxRate = Decimal(string: "0.0825")!
117+
118+
let subtotal = price * quantity
119+
let tax = subtotal * taxRate
120+
let total = subtotal + tax
121+
122+
let formatter = NumberFormatter()
123+
formatter.numberStyle = .currency
124+
print(formatter.string(from: total as NSDecimalNumber)!) // => "$64.62"
125+
```
126+
127+
Swift's `Decimal` is the recommended type for financial calculations in iOS and macOS applications.
128+
129+
### SQL
130+
131+
Most SQL databases treat decimal arithmetic as fundamental:
132+
133+
```sql
134+
SELECT
135+
price,
136+
quantity,
137+
price * quantity AS subtotal,
138+
(price * quantity) * 0.0825 AS tax,
139+
(price * quantity) * 1.0825 AS total
140+
FROM products
141+
WHERE id = 1;
142+
```
143+
144+
Database `NUMERIC` and `DECIMAL` types provide exact decimal arithmetic. When JavaScript applications query these values, they currently must receive them as strings (or as `Number`s, which may lose precision from the get-go) that are then converted to `Number` (which also loses precision), or use a userland decimal library.
145+
146+
Native JavaScript Decimal would allow seamless interchange with database decimal types.
147+
148+
### Why This Matters for JavaScript
149+
150+
JavaScript forces developers to choose between:
151+
152+
- Binary floats (precision errors, bugs, non-trivial knowledge of binary float problems and some countermeasures that may not always work)
153+
- Userland libraries (bundle size, coordination, performance)
154+
- Integer "cents" (cognitive overhead, internationalization issues)
155+
156+
The patterns in this cookbook reflect industry-standard practices that JavaScript developers should be able to use natively, just as developers in other major languages can.
157+
23158
## Common Patterns
24159

25160
This section demonstrates common patterns and best practices when working with Decimal.
26161

27162
### Working with Monetary Values
28163

29-
Always create Decimal values from strings to ensure exact representation:
164+
Create Decimal values from strings to ensure exact representation:
30165

31166
```javascript
32-
// Good: Create from string
33167
const price = new Decimal("19.99");
34168
const tax = new Decimal("0.0825");
35169

@@ -41,7 +175,7 @@ console.log(total.toFixed(2)); // => "21.64"
41175

42176
### Accumulating Values
43177

44-
When accumulating many values over the course of a calculation with several steps, use Decimal to avoid rounding errors:
178+
When accumulating many values in the course of a calculation with several steps, use Decimal to avoid rounding errors:
45179

46180
```javascript
47181
const transactions = ["10.50", "25.75", "3.99", "100.00", "45.25"];
@@ -111,7 +245,7 @@ console.log(tip); // => { amount: "6.83", total: "52.33" }
111245

112246
### Creating Decimal values
113247

114-
The most reliable way to create exact decimal values is from strings:
248+
The most reliable way to create Decimal values is from strings:
115249

116250
```javascript
117251
// From string (recommended for exact values)
@@ -171,15 +305,22 @@ try {
171305
}
172306
```
173307

174-
Very large integers beyond Number's safe range work perfectly:
308+
If one wants to ensure that a Decimal value represents, semantically, an integer, use `round` before convering to BigInt:
309+
310+
```javascript
311+
const fractionalDecimal = new Decimal("123.45"); // same as previous example
312+
fractionalDecimal.round().toBigInt(); // ...but doesn't throw
313+
```
314+
315+
Very large integers beyond `Number`'s safe range work perfectly:
175316

176317
```javascript
177318
const largeDecimal = new Decimal("99999999999999999999999999999999");
178319
const largeBigInt = largeDecimal.toBigInt();
179320
console.log(largeBigInt); // => 99999999999999999999999999999999n
180321
```
181322

182-
(However, there are some digit strings that Number can handle just fine, namely those with more than 34 significant digits that have compact representations as sums of powers of two.)
323+
However, there are some digit strings that Number can handle just fine, namely those with more than 34 significant digits that have compact representations as sums of powers of two.
183324

184325
## Financial Calculations
185326

0 commit comments

Comments
 (0)