[minor] Convert units exactly for every storage type - #226
Merged
Conversation
Unit factors, offsets, and metric prefixes reached a storage type through a
double. Decimal storage therefore carried factor errors as large as 2e-13, and a
150-digit factor such as DegreeToRadians was cut to 17 digits by the compiler.
Each generated factor is now parsed once per storage type from its metadata
literal and cached, and IUnit.ToBase, FromBase, the From{Unit} factories, and
every In(unit) use it. Decimal conversions are correct to 28 digits. Double
results are bit-identical for all but two factors, which moved to the nearest
double.
conversions.json accepts an exact fraction such as "5/18" as well as a decimal
literal, and a malformed value reports the new SEM009 error. Thirteen factors
that had been stored already rounded now carry their exact definitions.
Vector Length() and Distance() call StorageMath.Sqrt. Double, float, Half, and
the integer primitives keep the exact expression they had. Other storage types
refine the double estimate with Newton steps, so decimal gets 28 digits.
IUnit gains ToBaseFactorAs<T>() and ToBaseOffsetAs<T>() with default
implementations, so existing implementers don't break. StorageTypes now lists
decimal.
StorageConversionTests runs the same conversion and relationship checks for
double and decimal, and adding a storage type takes one line.
Claude-Session: https://claude.ai/code/session_01K5Bk9UjGdGUtC5C6qK5ZxD
|
This was referenced Sep 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
Unit conversions and vector lengths are now exact to the precision of the storage type, not just to the precision of
double. Withdecimalstorage, every conversion factor is correct to 28 digits andLength()returns √2 as1.4142135623730950488016887242.doubleandfloatbehave as before, apart from two factors that moved one step closer to their exact values.This is also what a non-IEEE storage type such as PreciseNumber needs. With PreciseNumber 2.0 as storage,
Length.FromFoot(1).In(Units.Inch)is exactly12, wheredoublegives12.000000000000002.Changes
ConversionsGeneratorandMagnitudesGeneratoremit a nestedValues<T>holder, parsed once for each closed generic type, next to the existingconst double. GeneratedFrom{Unit}factories,IUnit.ToBase<T>,FromBase<T>, and everyIn(unit)read it. Integer storage, and any type that can't parse the literal, falls back toT.CreateChecked(double).conversions.jsonaccepts a fraction such as"5/18". A malformed value reports the newSEM009error. Thirteen factors that were stored already rounded now carry their exact definitions: repeating ratios as fractions, and π-based factors as 150-digit literals. SquareMile, PoundForce, and PoundFoot were already exact and are unchanged.Length()andDistance()call the internalStorageMath.Sqrt.double,float,Half, and the integer primitives keep their previous expression exactly. Other types refine thedoubleestimate with Newton steps until the value stops changing.IUnit.ToBaseFactorAs<T>()andToBaseOffsetAs<T>()have default implementations, so existing implementers keep compiling.StorageTypesnow lists decimal.CLAUDE.mdanddocs/physics-generator.mddescribe the fraction syntax, the per-type factors, and SEM009.Risk
T.CreateChecked(constant)becomingValues<T>, thedoublesquare root becomingStorageMath.Sqrt, the 13 metadata constants, and theStorageTypeslist.PsiToPascalsis a long literal rather than a fraction. As a fraction,floatstorage would land further from the exact value, because the numerator would have to round to afloatbefore the division.QuantitiesGeneratorignoresconversionFactorwhen a unit also has amagnitude, andUnitsGeneratorapplies both. No unit has both today.Testing
StorageConversionTests<T>runs the same conversion and relationship checks fordoubleanddecimal, including exact decimal results such as a foot being 12 inches and a 3-4-0 vector having length 5. Adding a storage type takes one derived class.UnitFactorConsistencyTestschecks that every unit'sToBaseFactorAs<double>()matches itsToBaseFactorproperty.In(unit).Generate-AliasProps.ps1produces no content changes.🤖 Generated with Claude Code
https://claude.ai/code/session_01K5Bk9UjGdGUtC5C6qK5ZxD