<!--
Summary provided to audience:
-->
<style>
/* make code examples suck less */
pre code {
margin: 0 !important;
font-size: 1.25em !important;
}
pre {
padding: 0 !important;
text-align: left !important;
}
.align-left {
text-align: left !important;
}
.small-li li {
font-size: 0.85em !important;
}
</style>
<!-- .slide: data-cover -->
# Decimal & Measure
## A unified approach for numbers in JS
Jesse Alama & Ben Allen
(Igalia, in partnership with Bloomberg & Google)
jesse@igalia.com, ben@igalia.com
& Eemeli Aro (Mozilla)
February 2025
---
## [Decimal](https://github.com/tc39/proposal-decimal)
Exact decimal numbers to eliminate rounding errors frequently seen with binary floats when handling "human" numeric data, especially when calculating with them.
**Example:** 1.3 *really is* 1.3, not an appoximation thereof.
**Example:** 0.1 + 0.2 and 0.3 represent the same mathematical value.
---
## [Measure](https://github.com/tc39/proposal-measure)
Tag numbers with a unit (e.g., `"gram"`) and, optionally, a precision.
```javascript
let m = new Measurement("30", {unit: "centimeter"});
m.toString(); // "30 centimeters"
m.convertTo("centimeter", -2).toString(); // "30.00 centimeters"
let f = new Measurement("5.5", {unit: "foot"});
f.toString(); // "5.5 feet"
f.convertTo("foot-and-inch").toComponents();
// [ {value: 5, unit: "foot"}, {value: 6, unit: "inch"}]
```
---
## Shared design space
The two proposals address distinct needs, but share an interesting overlap: **both help representing numbers the way humans use numbers**.
- base-10 rounding
- precision
- units
<!--
* Decimal, as currently designed, lacks any notion of "precision" of a number. They're mathematical values.
* Example: There's no detectable difference between (Decimals constructed from) "1.2" and "1.20" in this setting.
* Measure allows us to handle numbers with precision.
* Example: `new Measurement(1.2, {precision: -2}).toString()` gives us "1.20".
-->
---
## Measure can use Decimal
Measure needs to define its underlying numeric representation. Intl currently uses _mathematical values_ to avoid floating-point errors.
Measure could instead directly use decimals, and Decimal objects could be upgradeable into Measure objects.
```javascript=
new Measure(new Decimal("1.2"), {unit: "gram", precision: -1})
```
---
# Learning from Temporal
---
## Learning from Temporal
Temporal introduces multiple concepts, and users can mix-and-match them: _time_, _dates_, _timezones_.
The API is strongly typed, with explicit conversion methods between the types, offering very good DX:
`PlainTime`, `PlainDate`, `PlainDateTime`, `ZonedDateTime`, `Instant`
---
## Learning from Temporal
Can we design a unified system across the two proposals, using a single underlying model but including different amount of information in different types?
---
_Base-10 decimal numbers_
<style>
.bl {
border-left: thin solid black;
}
.br {
border-right: thin solid black;
}
.bt {
border-top: thin solid black;
}
.bb {
border-bottom: thin solid black;
}
td:has(> table) {
padding: 0 !important;
}
td table th {
font-style: italic;
}
td table td, td table th {
padding: 8px !important;
width: 50%;
}
</style>
<table>
<tr>
<th colspan=2 rowspan=2></th>
<th colspan=2 class="br">Exposes precision?</th>
</tr><tr>
<th class="bl bb">No</th>
<th class="bl bb br">Yes</th>
</tr><tr>
<th rowspan=2 class="bb">Has a unit?</th>
<th class="bb bt">No</th>
<td class="bl bb">Decimal (normalized)</td>
<td class="bl br bb">Decimal (full IEEE-754)</td>
</tr><tr>
<th class="bb">Yes</th>
<td class="bl bb">Exact measures <small>(e.g. speed of light)</small></td>
<td class="bl br bb">Measure <small>(e.g. length of a stick)</small></td>
</tr>
</table>
---
_All numeric types?_
<table>
<colgroup>
<col span="2" style="width: 20%">
<col span="2" style="width: 30%">
</colgroup>
<tr>
<th colspan=2 rowspan=2></th>
<th colspan=2 class="br">Exposes precision?</th>
</tr><tr>
<th class="bl bb">No</th>
<th class="bl bb br">Yes</th>
</tr><tr>
<th rowspan=3 class="bb">Numeric type</th>
<th class="bb bt">float64</th>
<td class="bl bb">
<table>
<tr>
<th colspan=2>Has a unit?</th>
</tr><tr>
<th>No</th>
<th class="bl">Yes</th>
</tr><tr>
<td>Number</td>
<td class="bl">?</td>
</tr>
</table>
</td>
<td class="bl br bb">
<table>
<tr>
<th colspan=2>Has a unit?</th>
</tr><tr>
<th>No</th>
<th class="bl">Yes</th>
</tr><tr>
<td>?</td>
<td class="bl">Measure?</td>
</tr>
</table>
</td>
</tr><tr>
<th class="bb">integer</th>
<td class="bl bb">
<table>
<tr>
<th colspan=2>Has a unit?</th>
</tr><tr>
<th>No</th>
<th class="bl">Yes</th>
</tr><tr>
<td>BigInt</td>
<td class="bl">?</td>
</tr>
</table>
</td>
<td class="bl br bb">
<table>
<tr>
<th colspan=2>Has a unit?</th>
</tr><tr>
<th>No</th>
<th class="bl">Yes</th>
</tr><tr>
<td>?</td>
<td class="bl">Measure?</td>
</tr>
</table>
</td>
</tr><tr>
<th class="bb">base-10 float</th>
<td class="bl bb">
<table>
<tr>
<th colspan=2>Has a unit?</th>
<tr>
<th>No</th>
<th class="bl">Yes</th>
<tr>
<td>Decimal (normalized)</td>
<td class="bl">Exact measures</td>
</table>
</td>
<td class="bl br bb">
<table>
<tr>
<th colspan=2>Has a unit?</th>
<tr>
<th>No</th>
<th class="bl">Yes</th>
<tr>
<td>Decimal (full IEEE-754)</td>
<td class="bl">Measure</td>
</table>
</td>
</tr>
</table>
---
## Do we really need all of that?
Everything could be expressed by a single class:
- "1"/"u" could be a valid unit, meaning that the _unitless_ number 2.34 is actually 2.34 with unit 1.
- _Infinity_ could be a valid precision, meaning that the an exact number is actually a number with infinite precision.
---
## Do we really need all of that?
Having separate types depending on the level of information has significant DX advantages:
- No need to manually validate which information is present/absent in your inputs every time
- Better static type checking
- _Additional information limits capabilities_
---
## Additional information limits capabilities
When it comes to "doing math with your numbers", having more information can mean you can do fewer operations with it.
- 1.23 + 0.04 = 1.27 ✅
- 1.23 (3 significant digits) + 0.040 (2 significant digits) = ???
- 1.23 meters + 0.04 watts = 💥
<!--
Mention that for precision propagation there isn't a single good answer, and that with multiplication between units then you need to support compound units.
-->
---
## We need _at least_ two classes
<div style="display: flex">
<div style="flex: 1 1 0; border-right: thin solid black">
**Decimal (normalized)**
- `.add()`, `.subtract()`, ...
- based on IEEE-754 limits
- no tracking of precision
</div><div style="flex: 1 1 0">
**Measure (with precision)**
- backed by a decimal
- no arithmetic
- `.convertTo("meters")`
- has a _static_ notion of precision
</div></div>
---
## We need to be able to convert between them
```javascript
measure.toDecimalObject();
Measure.from({
value: decimal,
significantDigits: 3,
unit: "liters"
});
decimal.withUnit("liters", 3);
```
<div style="border: 3px solid red; max-width: 50%; padding: 8px; float: right; font-size: .5em">⚠️ All code examples at this point are pure speculation</div>
---
## Open questions
---
## All numeric types?
Especially _if_ Measure will support custom units, there is desire for it to also support being backed by a BigInt.
Do we need it? `BigIntMeasure`/`DecimalMeasure`/`NumberMeasure`, or a single `Measure` with a `.type: "bigint" | "decimal" | "number"` property?
<div style="border: 3px solid red; max-width: 50%; padding: 8px; float: right; font-size: .5em">⚠️ All names at this point are pure speculation</div>
---
## Do we need "decimal with precision" class?
<span style="display:inline-block;text-align:left">
<code>Decimal</code> <code>.withPrecision(3)</code><br>
→ <code>FullDecimal</code> <code>.withUnit("feet")</code><br>
→ <code>Measure</code>
</span>
It would not support aritmetic, to avoid picking a "blessed" precision propagation strategy.
<div style="border: 3px solid red; max-width: 50%; padding: 8px; float: right; font-size: .5em">⚠️ All names at this point are pure speculation</div>
---
## Next steps
---
## Moving the proposals forward
Decimal & measure overlap, but they are justified independently.
* **Option 1:** Keep them as separate proposals, designed in tight collaboration.
* **Option 2:** Merge the two proposals (during stage 1? later?)
A hypothetical unified proposal readme: https://notes.igalia.com/tc39-unified-measure-and-decimal
<!-- **Option 2.7:** Keep them as separate proposals, designed in tight collaboration, and move them together through the process. -->
---
## Proposing a different first step: Amount (Eemeli)
- Consider "amount" instead of "measure"
- Opaque value, with separate fields for dimension + precision
- Solves all presented Measure use cases
- Provides a decimal representation of numerical values for interchange
- Initially: No operations, no conversions
- Extensible with methods for operations & conversions, or a Decimal value
---
```typescript
class Amount {
unit?: string;
currency?: string;
significantDigits?: number;
// Validates value fits into Decimal restrictions
// Freezes self
constructor(value: number | BigInt | string, options?)
// Stringifies value as numerical string
toString(): string
// Defined in ECMA-402
toLocaleString(locales, options): string
}
```
---
Discusion
{"type":"slide","slideOptions":{"transition":"slide","theme":"igalia","controlsLayout":"edges","slideNumber":"c/t"}}