Skip to content

Add baby CAS unit conversion#89

Draft
jessealama wants to merge 3 commits intomainfrom
baby-cas-unit-conversion
Draft

Add baby CAS unit conversion#89
jessealama wants to merge 3 commits intomainfrom
baby-cas-unit-conversion

Conversation

@jessealama
Copy link
Copy Markdown
Collaborator

Adds a lightweight unit conversion system ("baby CAS") to the Amount proposal. The spec text introduces a static conversion factor table covering length, mass, volume, temperature, area, speed, concentration, and digital units, along with the ConvertUnitValue abstract operation that performs exact rational arithmetic over these factors. A TypeScript generator script (scripts/generate-conversion-table.ts) derives the ecmarkup table rows from CLDR unit data, ensuring the spec table stays in sync with upstream definitions.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 23, 2026

PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://tc39.github.io/proposal-amount/pr-preview/pr-89/

Built to branch gh-pages at 2026-03-27 13:05 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@jessealama jessealama force-pushed the baby-cas-unit-conversion branch 4 times, most recently from 0404a2e to 42ca8a2 Compare March 27, 2026 09:36
Conversion factors are stored as integer Numerator/Denominator pairs.
ConvertUnitValue combines source and target factors over a common
denominator and delegates to ApplyUnitConversion, which computes
(value × numerator + offset) / denominator using Number arithmetic.
@jessealama jessealama force-pushed the baby-cas-unit-conversion branch from 42ca8a2 to 4fc61a6 Compare March 27, 2026 09:48
Split the offset argument into offsetNumerator/offsetDenominator and
reduce conversionNumerator/conversionDenominator by their GCD before
the floating-point arithmetic. ConvertUnitValue now passes the scale
factor and offset as independent rationals.
Copy link
Copy Markdown
Member

@gibson042 gibson042 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I mentioned in the ECMA-402 meeting, I think this is doing far too much. If we pursue such an approach, I would instead expect it to take the form of expression-aware consumption of units.xml, from which point the specification could either require mathematical value calculations with a final conversion to Number or an up-front elementary arithmetic symbolic reduction followed by Number calculations. And in neither case would I expect to duplicate CLDR contents into large tables in ECMA specifications.

For a maximally complex example (i.e., involving both a non-unit factor and a non-zero offset), consider converting 100°C to Fahrenheit, which relies upon the following definitions in reverse order:

<convertUnit source='fahrenheit' baseUnit='kelvin' factor='5/9' offset='2298.35/9' systems="ussystem uksystem"/>
<convertUnit source='celsius' baseUnit='kelvin' offset='273.15' systems="si metric"/>
  • A naïve Number calculation gives the wrong answer: ((celsiusInput * (celsius.factor ?? 1) + (celsius.offset ?? 0)) - (fahrenheit.offset ?? 0)) / (fahrenheit.factor ?? 1) = ((100 * (undefined ?? 1) + (273.15 ?? 0)) - (2298.35/9 ?? 0)) / (5/9 ?? 1) = ((100 * 1 + 273.15) - 2298.35/9) / (5/9) = 211.99999999999997 = 211.99999999999997𝔽.
  • A mathematical value calculation gives the right answer: ((100 × 1 + 273.15) − 2298.35÷9) ÷ (5÷9) = ℝ(212) → 212𝔽.
  • Number calculation of the elementary arithmetic reduction of the expression also gives the right answer: ((celsiusInput × 1 + 273.15) - 2298.35÷9) ÷ (5÷9) = ((celsiusInput × 9 + 273.15 × 9) - 2298.35) ÷ 5 = (celsiusInput × 9 + 160) ÷ 5 = celsiusInput × 1.8 + 32 → 100 * 1.8 + 32 = 212 = 212𝔽.

Further, because arithmetic reduction in the mathematical value domain is guaranteed to be result-preserving, it is equally applicable to both sensible approaches—the above mathematical value calculation is exactly equivalent to 100 × 1.8 + 32 = ℝ(212) → 212𝔽. And since only linear conversions are in scope, that means conversion requires at most one multiplication and one addition—and even further still, given the current contents of units.xml, addition is necessary only for temperature conversions involving Celsius and/or Fahrenheit (every other non-special conversion is possible with just a single multiplication).

However, note that the mathematical value calculation and Number calculation of the elementary arithmetic reduction approaches are not definitionally equivalent—converting 80063993375475600°C with the former would produce 144115188075856128𝔽 (ℝ(144115188075856112) being snapped per the Number value for x from exactly halfway between two Number values with a mutual separation of 32 to that [higher] one with even significand), while the latter would produce 144115188075856096𝔽 (80063993375475600 * 1.8 first snapping midpoint ℝ(144115188075856080) down to even-significand 144115188075856064𝔽 in Number::multiply and then the subsequent + 32 effecting no further rounding).

Note also than even mathematical perfection will not eliminate surprises with rounding modes that are not based on "nearest" behavior (because e.g. 7 inches converts to 0.5833333333333333𝔽 feet, which converts to 6.999999999999999𝔽 inches). But I don't think such repeated conversions are in scope, which means both approaches are equally viable, especially given that only temperature conversions would be subject to intermediate rounding in the Number-calculations one, and even then the error would be like any other binary64 operation. What we're left with seems like immaterial end-user differences and a tradeoff between easy-to-specify mathematical value operations and easy-to-implement Number operations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants