Conversation
|
0404a2e to
42ca8a2
Compare
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.
42ca8a2 to
4fc61a6
Compare
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.
gibson042
left a comment
There was a problem hiding this comment.
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.
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.