Add std/dec64 and std/dec64math (Douglas Crockford DEC64 decimal floating point)#25788
Open
jonasohrn wants to merge 5 commits into
Open
Add std/dec64 and std/dec64math (Douglas Crockford DEC64 decimal floating point)#25788jonasohrn wants to merge 5 commits into
jonasohrn wants to merge 5 commits into
Conversation
std/dec64 implements Douglas Crockford's DEC64 (https://www.crockford.com/dec64.html) as a `distinct int64`: 56-bit signed coefficient, 8-bit signed exponent, NaN at exponent -128. Provides arithmetic (+, -, *, /, unary -), comparison (==, <, <=, cmp, hash), parsing/$ round-trip, a `'dec` custom literal, mixed (Dec64, int) operator overloads, and decimal-native floor/ceil/round/trunc/abs/sign and integer-exponent pow that stay in pure decimal throughout. The equal-zero-exponent fast path for `+` embeds Crockford's verbatim x64 / ARM64 inline-assembly snippets via `{.emit.}`, gated on amd64+gcc/clang and arm64+gcc/clang. A pure-Nim equivalent runs on every other backend including JS. `-d:nimDec64NoAsm` forces the pure-Nim path everywhere, which the test suite uses to verify both paths produce identical results. std/dec64math provides the elementary transcendentals: sqrt, exp, ln/log, log2, log10, sin, cos, tan, arcsin, arccos, arctan, arctan2, sinh, cosh, tanh, pow(Dec64, Dec64), nthRoot, and exact-integer factorial. Transcendentals delegate to std/math via a float64 round-trip, trading ~1 ulp at Dec64's last decimal digit for a pure- Nim implementation that wraps the battle-tested libm rather than reimplementing polynomials. A precision-preserving variant `dec64FromFloatPrecise` is exposed alongside the fast scale-and-round `dec64FromFloat` for callers who need shortest-round-trip fidelity. Tests cover packing, NaN propagation, alignment overflow, the 128-bit multiplication path, division precision, parse/$ round-trip, hash equality after canonicalization, the inline-asm and pure-Nim paths producing equal results, native-vs-float-trip parity, and std/math agreement to 1e-14 relative on a sampled domain. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inline asm across {x64, arm64} x {gcc, clang} x {C, C++} isn't worth
the maintenance burden for shaving a few cycles off the equal-zero-
exponent `+` case. Apple's clang++ on darwin-arm64 rejects the `"x2"`
clobber, breaking CI; the pure-Nim path covers it. Also folded
`addImpl` into `+` so there's a single execution path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Aligns the textual output with std/formatfloat. Dec64 values now print identically to their float64 equivalent (e.g. dec64(8, -8) becomes "8e-8" rather than "0.00000008"). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Construction now packs verbatim — trailing zeros stay, so 1.5 + 1.5 prints "3.0" and 1.25 * 4 prints "5.00". `==` uses Crockford's subtract-then-check to keep value equality across encodings; `hash` canonicalises lazily to stay consistent. Multiplication switches to nearest-even rounding. Add `significantDigits` / `fractionalDigits` to introspect carried precision. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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
Adds two new stdlib modules implementing Douglas Crockford's DEC64 64-bit decimal floating-point type:
std/dec64— type, arithmetic, comparison, hashing,$/parse,'deccustom literal, mixed(Dec64, int)overloads, decimal-nativefloor/ceil/round/trunc/abs/sign/integer-exponentpow.std/dec64math— elementary transcendentals (sqrt,exp,ln/log,log2,log10,sin,cos,tan,arcsin,arccos,arctan,arctan2,sinh,cosh,tanh,pow(Dec64, Dec64),nthRoot, exact-integerfactorial). Transcendentals delegate tostd/mathvia a float64 round-trip, trading ~1 ulp atDec64's last decimal digit for a pure-Nim implementation that wrapslibmrather than reimplementing polynomials. A precision-preservingdec64FromFloatPreciseis exposed alongside the fast scale-and-rounddec64FromFloatfor callers who need shortest-round-trip fidelity.Small footprint: ~510 lines of executable Nim across both modules, plus ~210 lines of comments. Tests are another ~350 lines.
Relation to RFC #308
This PR is a partial response to nim-lang/RFCs#308 ("Add decimal to standard library", Accepted RFC). That RFC primarily discusses IEEE 754-2008 decimal128 (128-bit, 34 digits) as the target format.
This PR instead implements Crockford's DEC64 (64-bit, ~16.8 digits) — a smaller, faster alternative aimed at money and human-facing measurements where DEC64's range and precision are sufficient.
An IEEE 754 decimal128 implementation remains a valuable follow-up and could coexist as e.g.
std/decimal128; the two address different precision/size tradeoffs.Why DEC64 in stdlib (not just Nimble)
DEC64 is a foundational numeric type — money, measurements, human-facing decimal values where binary float rounding is a bug. As a stdlib module it composes with
tables,json,strutils, etc. without a Nimble dependency.Test plan
nim c -r tests/stdlib/tdec64.nim(orc + refc)nim c -r tests/stdlib/tdec64math.nim(orc + refc)nim c -r -d:release tests/stdlib/tdec64math.nimnim doc lib/pure/dec64.nimandlib/pure/dec64math.nim— runnableExamples passdec64FromFloat(fast) anddec64FromFloatPrecise(string round-trip) agree to within 2e-15 relative on inexact inputsOpen to feedback on
Dec64vsDEC64for the type name — currentlyDec64per Nim's PascalCase convention; the spec uses both spellings.dec64FromFloatPrecisebelongs in the public API. It duplicatesdec64.nim'sdec64(f: float)constructor exactly; could be removed in favor of pointing precision-conscious callers at the constructor.sinh/cosh/tanh) — included for symmetry withstd/math; happy to drop if reviewers prefer a tighter surface.Tests cover packing, NaN propagation, alignment overflow, the 128-bit multiplication path, division precision, parse/$ round-trip, hash equality after canonicalization, native-vs-float-trip parity, and std/math agreement to 1e-14 relative on a sampled domain.