{"id":50916018,"url":"https://github.com/alexandroskyriakakis/zerodecimal","last_synced_at":"2026-06-18T17:00:41.512Z","repository":{"id":364649661,"uuid":"1267152567","full_name":"AlexandrosKyriakakis/zerodecimal","owner":"AlexandrosKyriakakis","description":"Zero-allocation, panic-free decimal arithmetic for HFT-grade Go. No big.Int, no GC pressure, fastest in its class.","archived":false,"fork":false,"pushed_at":"2026-06-13T21:43:43.000Z","size":317,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-17T16:58:55.343Z","etag":null,"topics":["decimal","golang","panic-free","pgo","zero-allocation"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AlexandrosKyriakakis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-12T09:14:56.000Z","updated_at":"2026-06-17T00:27:13.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/AlexandrosKyriakakis/zerodecimal","commit_stats":null,"previous_names":["alexandroskyriakakis/zerodecimal"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/AlexandrosKyriakakis/zerodecimal","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AlexandrosKyriakakis%2Fzerodecimal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AlexandrosKyriakakis%2Fzerodecimal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AlexandrosKyriakakis%2Fzerodecimal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AlexandrosKyriakakis%2Fzerodecimal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AlexandrosKyriakakis","download_url":"https://codeload.github.com/AlexandrosKyriakakis/zerodecimal/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AlexandrosKyriakakis%2Fzerodecimal/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34499412,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-18T02:00:06.871Z","response_time":128,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["decimal","golang","panic-free","pgo","zero-allocation"],"created_at":"2026-06-16T15:01:43.044Z","updated_at":"2026-06-18T17:00:41.493Z","avatar_url":"https://github.com/AlexandrosKyriakakis.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# zerodecimal\n\nZero-allocation, panic-free, fixed-point decimals for latency-critical Go.\n\n## Why another decimal library\n\n- **Strictly zero heap allocations.** Parsing, arithmetic, comparison,\n  rounding, conversions, and every Unmarshal/Scan path perform exactly zero\n  heap allocations — success and error paths alike — enforced by\n  `testing.AllocsPerRun` gates in the default test suite\n  ([alloc_test.go](alloc_test.go)).\n- **Faster than every Go decimal library we could find.** The committed\n  benchstat comparisons show a −41% time geomean against jokruger/dec128 and\n  −50% against quagmt/udecimal — the two nearest rivals — and −92% against\n  shopspring/decimal ([benchmarks/bench-vs-\\*.txt](benchmarks/)).\n- **Bit-exact.** Every operation is differentially checked against\n  shopspring/decimal's unbounded arithmetic — including an *iff* proof for\n  every returned overflow — deterministically in the default suite\n  ([crosscheck_test.go](crosscheck_test.go)) and by 23 fuzz targets\n  ([fuzz_test.go](fuzz_test.go)).\n- **Panic-free.** Fallible operations return zero-allocation sentinel errors\n  ([errors.go](errors.go)) and the fuzz suite requires every target to be\n  total — no input, including garbage binary payloads, may panic the library.\n\n## Install\n\n```sh\ngo get github.com/AlexandrosKyriakakis/zerodecimal\n```\n\n```go\nimport \"github.com/AlexandrosKyriakakis/zerodecimal\" // package zerodecimal\n```\n\nRequires Go 1.26+. The library has zero runtime dependencies.\n\n```go\nprice, err := zerodecimal.NewFromString(\"99.99\")\nif err != nil {\n    return err\n}\nqty := zerodecimal.NewFromInt(3)\n\ntotal, err := price.Mul(qty)\nif err != nil {\n    return err\n}\nfmt.Println(total)                // 299.97\nfmt.Println(total.StringFixed(4)) // 299.9700\n```\n\nRunnable examples for parsing, arithmetic, rounding, JSON, and SQL live in\n[example_test.go](example_test.go).\n\n## Design\n\n```go\ntype Decimal struct {\n    coef u128  // |value| · 10^prec, 0 ≤ coef \u003c 2^128\n    neg  bool\n    prec uint8 // fractional digits, 0..19\n}\n```\n\nA Decimal is a 24-byte pointer-free value: copy it freely, compare it cheaply,\npack it densely. The domain is |value| \u003c 2^128 / 10^prec — up to 39\nsignificant digits with up to 19 fractional. There is **no `big.Int` anywhere\nin the package**: every operation runs on fixed-width 128/256-bit integer\nmath, so nothing can escape to the heap and out-of-domain results return\n`ErrOverflow` instead of degrading into arbitrary-precision slowness. The\nzero value is the canonical decimal zero, ready to use; no operation produces\na negative zero.\n\n**Reciprocal division is the headline optimization.** Decimal rescaling,\nrounding, formatting, and division all reduce to dividing by powers of ten,\nand zerodecimal never asks the hardware divider to do it: 64-bit dividends\nuse precomputed Granlund–Montgomery–Warren multiply-high magics, and\n128/256-bit dividends chain Möller–Granlund 2-by-1 steps off a precomputed\nreciprocal table ([div10.go](div10.go), tables generated and re-proven\nagainst `bits.Div64` and `big.Int` in [tables_test.go](tables_test.go)).\nA multiply-high plus a shift replaces an 18-cycle `DIV` — and for 128-bit\ndividends, two *dependent* `DIV`s — which is where most of the headroom over\nudecimal comes from.\n\n`Div` uses **adaptive precision**: the result is the exact quotient truncated\nat the largest precision ≤ `DefaultPrec` (19 by default) whose coefficient\nstill fits 128 bits, so huge quotients degrade precision gracefully and\n`ErrOverflow` is reserved for integer quotients that genuinely exceed 2^128.\n\nBecause `==` compares representations, an arithmetic result of `1.50` differs\nfrom a parsed `1.5` under `==`; use `Equal` or `Cmp` for numeric comparison.\nParsing trims trailing fractional zeros; arithmetic never does (it would tax\nthe hot path); formatting trims at output.\n\n## Error model\n\nAll sentinels live in [errors.go](errors.go), are returned bare (never\nwrapped, except `Scan`'s unsupported-type message), and match with\n`errors.Is`. The constructors and arithmetic operations have panicking twins\nfor call sites with proven bounds; rows marked `—` below have none.\n\n| Operation | Possible sentinels | Panicking twin |\n| --- | --- | --- |\n| `New` | `ErrOverflow`, `ErrPrecOutOfRange` | `MustNew` |\n| `NewFromString`, `ParseBytes` | `ErrEmptyString`, `ErrMaxStrLen`, `ErrInvalidFormat`, `ErrOverflow`, `ErrPrecOutOfRange` | `RequireFromString` |\n| `NewFromStringTrunc`, `ParseBytesTrunc` | `ErrEmptyString`, `ErrMaxStrLen`, `ErrInvalidFormat`, `ErrOverflow` | — |\n| `NewFromFloat`, `NewFromFloat32` | `ErrInvalidFloat`, `ErrOverflow`, `ErrPrecOutOfRange` | `RequireFromFloat` |\n| `NewFromHiLo` | `ErrPrecOutOfRange` | — |\n| `Add`, `Sub`, `Mul` | `ErrOverflow` | `MustAdd`, `MustSub`, `MustMul` |\n| `Div` | `ErrDivideByZero`, `ErrOverflow` | `MustDiv` |\n| `QuoRem`, `Mod` | `ErrDivideByZero`, `ErrOverflow` | `MustQuoRem`, `MustMod` |\n| `Sum`, `Avg` | `ErrOverflow` | `MustSum`, `MustAvg` |\n| `IntPart` | `ErrIntPartOverflow` | — |\n| `UnmarshalText`, `UnmarshalJSON` | the parse sentinels | — |\n| `UnmarshalBinary` | `ErrInvalidBinaryData` | — |\n| `Scan` | the parse sentinels, `ErrInvalidFloat`, `ErrScanNil`, `ErrScanType` | — |\n\nEverything else is infallible: `NewFromInt`/`NewFromInt32`/`NewFromUint64`,\n`Neg`, `Abs`, `Sign`, the `Is*` predicates, `Cmp` and the comparison family,\n`Min`/`Max`, the entire rounding family, `Prec`, `ToHiLo`, `String`,\n`StringFixed`, `AppendFixed`, and `InexactFloat64`. `AppendText`,\n`AppendBinary`, the `Marshal*` methods, and `Value` return an error only to\nsatisfy their interfaces — it is always nil.\n\n## Allocation guarantees\n\nExactly what [alloc_test.go](alloc_test.go) enforces with\n`testing.AllocsPerRun` on every `make test` run, across six value shapes\n(small integers, typical prices, full 19-digit precision, extreme precision\nmismatch, near-2^128 coefficients, negatives), on success *and* error paths:\n\n| Allocations | Operations | Gate |\n| --- | --- | --- |\n| **exactly 0** | `NewFromString`, `ParseBytes`, `Add`, `Sub`, `Mul`, `Div`, `QuoRem`, `Mod`, `Cmp`, `Equal`, `Neg`, `Abs`, `Sign`, `Round`, `RoundBank`, `RoundUp`, `RoundDown`, `RoundCeil`, `RoundFloor`, `Truncate`, `Floor`, `Ceil`, `IntPart`, `InexactFloat64`, `NewFromFloat`, `AppendText`, `AppendFixed`, `AppendBinary`, `Min`, `Max`, `MustAdd`, `UnmarshalText`, `UnmarshalJSON`, `UnmarshalBinary`, `Scan` (string and `[]byte`) | `TestAllocsZero` |\n| **exactly 1** | `String` (outside the cache window), `StringFixed` — the returned string itself | `TestAllocsOne` |\n| **exactly 1** | `MarshalText`, `MarshalJSON`, `MarshalBinary` — the returned slice, sized exactly | `TestAllocsCodecMarshal` |\n| **exactly 0** | `String` and `Value` on values inside the small-value cache window (−1000.00..+1000.00, ≤ 2 places) | `TestAllocsStringCached`, `TestAllocsSQLValueCached` |\n| **exactly 2** | `Value` outside the cache window — the canonical string plus boxing it into `driver.Value` | `TestAllocsSQLValueUncached` |\n\nThe counts are asserted as *exact*, not upper bounds, so a regression in\neither direction fails the suite. Since the steady state allocates nothing,\nzerodecimal generates no GC pressure regardless of `GOGC`.\n\n## Parsing rules\n\nGrammar: `['+'|'-'] digits ['.' digits] [('e'|'E') ['+'|'-'] digits]`, ASCII\nonly, at most 200 bytes.\n\nAccepted:\n\n- plain literals: `\"123\"`, `\"-4.20\"`, `\"+1\"`, redundant zeros (`\"00012.3400\"` → `12.34`)\n- scientific notation: `\"1.23e4\"` → `12300`, `\"1E-7\"` → `0.0000001` (required for JSON float interop)\n- up to 39 significant digits: `\"340282366920938463463374607431768211455\"` (= 2^128−1) parses; one more unit is `ErrOverflow`\n\nRejected:\n\n- `\"\"` → `ErrEmptyString`; input over 200 bytes → `ErrMaxStrLen`\n- `\"1.\"` and `\".1\"` → `ErrInvalidFormat`: **both sides of the dot need a digit** (deliberately stricter than shopspring), as do `\".\"`, `\"-\"`, `\"1..2\"`, `\"1e\"`, `\"1e+\"`\n- whitespace, underscores, non-ASCII digits, `\"NaN\"`, `\"Inf\"` → `ErrInvalidFormat`\n- more than 19 fractional digits → `ErrPrecOutOfRange` (strict variants)\n\nThe `Trunc` variants (`NewFromStringTrunc`, `ParseBytesTrunc`) replace\n`ErrPrecOutOfRange` with truncation toward zero at 19 fractional digits\n(possibly to exactly zero) and accept any mantissa within the 200-byte input\ncap (`ErrMaxStrLen` still applies) whenever the truncated value is\nrepresentable; grammar violations and genuinely unrepresentable values still\nerror. Results are always canonical: trailing\nfractional zeros are trimmed (`\"1.500\"` parses identically to `\"1.5\"`) and\nparsing never allocates — not even on failure.\n\n## Rounding modes\n\n`places` counts fractional digits; `places ≥ d.Prec()` returns `d` unchanged.\nThe whole family is infallible — the increment can never overflow — and\nrounding a negative value to zero yields the canonical unsigned zero.\n\n| Method | Mode | `2.5` → | `3.5` → | `-2.5` → |\n| --- | --- | --- | --- | --- |\n| `Round(0)` | half away from zero (shopspring `Round`) | `3` | `4` | `-3` |\n| `RoundBank(0)` | half to even (banker's) | `2` | `4` | `-2` |\n| `RoundUp(0)` | away from zero | `3` | `4` | `-3` |\n| `RoundDown(0)` / `Truncate(0)` | toward zero | `2` | `3` | `-2` |\n| `RoundCeil(0)` | toward +∞ | `3` | `4` | `-2` |\n| `RoundFloor(0)` | toward −∞ | `2` | `3` | `-3` |\n\n`Floor()` and `Ceil()` are `RoundFloor(0)` and `RoundCeil(0)`. Every mode is\npinned tie-by-tie against its shopspring equivalent in\n[crosscheck_test.go](crosscheck_test.go) and fuzzed in\n[fuzz_test.go](fuzz_test.go).\n\n## Benchmarks\n\n\u003cpicture\u003e\n  \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"benchmarks/comparison-dark.svg\"\u003e\n  \u003cimg alt=\"Geomean latency of zerodecimal versus other Go decimal libraries (ns/op, shorter is faster); zerodecimal and zerodecimal+PGO are the two fastest\" src=\"benchmarks/comparison-light.svg\"\u003e\n\u003c/picture\u003e\n\nThe comparative suite lives in [benchmarks/](benchmarks/) — a **separate Go\nmodule**, so the competitor dependencies never touch the library's `go.mod`.\nFull committed results: [bench-vs-dec128.txt](benchmarks/bench-vs-dec128.txt),\n[bench-vs-udecimal.txt](benchmarks/bench-vs-udecimal.txt),\n[bench-vs-govalues.txt](benchmarks/bench-vs-govalues.txt),\n[bench-vs-shopspring.txt](benchmarks/bench-vs-shopspring.txt),\n[bench-vs-alpacadecimal.txt](benchmarks/bench-vs-alpacadecimal.txt),\n[bench-vs-ericlagergren.txt](benchmarks/bench-vs-ericlagergren.txt);\nmethodology and the deliberate semantic asymmetries are documented in\n[benchmarks/README.md](benchmarks/README.md). The chart above is regenerated\nfrom those files with `make -C benchmarks chart`.\n\nAgainst jokruger/dec128 — the closest competitor (also a 128-bit,\nzero-allocation fixed-point design) — zerodecimal wins the geomean by −41% and\nleads on every op × shape row except four sub-0.1 ns small-shape rows at\ndocumented floors (`Parse`, `RoundBank`, `Truncate` at `small_int`, and\n`MarshalBinary` at `near_max`):\n\n```\ngoos: darwin\ngoarch: arm64\ncpu: Apple M1 Pro\n                          │    dec128    │             zerodecimal             │\n                          │    sec/op    │   sec/op     vs base                │\nAdd/typical_price-10         5.685n ± 0%   2.290n ± 0%  -59.72% (p=0.000 n=10)\nMul/typical_price-10         3.768n ± 0%   2.429n ± 0%  -35.54% (p=0.000 n=10)\nDiv/typical_price-10         9.123n ± 0%   7.128n ± 1%  -21.86% (p=0.000 n=10)\nQuoRem/typical_price-10      7.524n ± 0%   3.260n ± 1%  -56.68% (p=0.000 n=10)\nCmp/typical_price-10         4.194n ± 0%   2.162n ± 0%  -48.45% (p=0.000 n=10)\nParse/typical_price-10      10.045n ± 1%   8.993n ± 6%  -10.47% (p=0.000 n=10)\nString/typical_price-10      26.24n ± 3%   22.89n ± 2%  -12.75% (p=0.000 n=10)\ngeomean                      13.15n        7.759n       -40.98%\n```\n\nAgainst quagmt/udecimal, zerodecimal is faster on 89 of the 90 op × shape rows\nand statistically tied on the remaining one (`MarshalJSON/small_int`):\n\n```\n                          │   udecimal   │             zerodecimal             │\n                          │    sec/op    │   sec/op     vs base                │\nAdd/typical_price-10         4.723n ± 1%   2.290n ± 0%  -51.51% (p=0.000 n=10)\nMul/typical_price-10         6.494n ± 0%   2.429n ± 0%  -62.60% (p=0.000 n=10)\nDiv/typical_price-10        12.990n ± 0%   7.128n ± 1%  -45.12% (p=0.000 n=10)\nQuoRem/typical_price-10     13.515n ± 0%   3.260n ± 1%  -75.88% (p=0.000 n=10)\nCmp/typical_price-10         5.334n ± 0%   2.162n ± 0%  -59.47% (p=0.000 n=10)\nParse/typical_price-10      14.470n ± 1%   8.993n ± 6%  -37.85% (p=0.000 n=10)\nString/typical_price-10      33.04n ± 2%   22.89n ± 2%  -30.71% (p=0.000 n=10)\ngeomean                      15.67n        7.759n       -50.48%\n```\n\nAgainst shopspring/decimal, the de-facto standard:\n\n```\n                          │  shopspring   │             zerodecimal             │\n                          │    sec/op     │   sec/op     vs base                │\nAdd/typical_price-10        41.660n ± 2%   2.290n ± 0%  -94.50% (p=0.000 n=10)\nMul/typical_price-10        41.785n ± 1%   2.429n ± 0%  -94.19% (p=0.000 n=10)\nDiv/typical_price-10       216.250n ± 1%   7.128n ± 1%  -96.70% (p=0.000 n=10)\nQuoRem/typical_price-10    114.050n ± 2%   3.260n ± 1%  -97.14% (p=0.000 n=10)\nCmp/typical_price-10         4.476n ± 2%   2.162n ± 0%  -51.69% (p=0.000 n=10)\nParse/typical_price-10      76.995n ± 2%   8.993n ± 6%  -88.32% (p=0.000 n=10)\nString/typical_price-10     108.80n ± 1%   22.89n ± 2%  -78.96% (p=0.000 n=10)\ngeomean                      95.51n        7.759n       -92.23%\n```\n\nAllocations are 0 on every row where any competitor manages 0, and 0 on many\nwhere they do not (e.g. udecimal's `Mul/large` allocates 160 B/op across 4\nallocations; zerodecimal allocates nothing).\n\n### Known trade-offs\n\nAllocation floors accepted by design (from\n[benchmarks/README.md](benchmarks/README.md)):\n\n- **`String`: 1 alloc** outside the cache window — a string-returning API\n  must allocate its immutable result; the rendering itself is a stack buffer.\n- **`MarshalText`/`MarshalJSON`/`MarshalBinary`: 1 alloc** — callers own and\n  may mutate marshal results, so sharing cached bytes is off the table; the\n  slice is sized exactly.\n- **`Value`: 2 allocs** outside the cache window — the canonical string plus\n  boxing into the `driver.Value` interface; there is no cheaper portable shape.\n\n## PGO\n\nPGO attaches to binaries, not libraries — so zerodecimal cannot ship it, but\nyour build can claim it. The hot paths are written PGO-friendly: no\ninterfaces or indirect calls anywhere (devirtualization is never needed), and\nthe slow arms (`addSlow`, `mulSlow`, the multi-limb division bodies) are\ndeliberately outlined into small functions that profile-driven inlining can\npromote straight into *your* hot loops past the default inlining budget.\n\n1. Collect a CPU profile from production or a representative load:\n   `pprof.StartCPUProfile` / `curl .../debug/pprof/profile \u003e default.pgo`.\n2. Drop it at your main package root as `default.pgo` (picked up by\n   `go build` automatically, i.e. `-pgo=auto`) or pass `-pgo=/path/to.pprof`.\n3. Rebuild and ship.\n\nThe committed [benchmarks/bench-pgo.txt](benchmarks/bench-pgo.txt) shows what\nthe benchmark binary itself gains when rebuilt against its own profile\n(`make bench-pgo`): a −8.7% time geomean, with the arithmetic and division\ncores gaining the most as their outlined slow arms inline into the measured\ncall sites. No op × shape row regresses — the `Cmp` family, which used to lose\na little to PGO's layout choices, now improves slightly after the branchless\nrewrite:\n\n```\n                          │   default   │                 pgo                 │\n                          │   sec/op    │   sec/op     vs base                │\nAdd/typical_price-10        2.287n ± 1%   2.084n ± 0%   -8.86% (p=0.000 n=10)\nSub/typical_price-10        2.754n ± 0%   2.247n ± 0%  -18.43% (p=0.000 n=10)\nMul/large-10                4.886n ± 1%   3.634n ± 1%  -25.62% (p=0.000 n=10)\nDiv/typical_price-10        7.151n ± 1%   5.444n ± 0%  -23.87% (p=0.000 n=10)\nQuoRem/typical_price-10     3.251n ± 0%   2.730n ± 1%  -16.01% (p=0.000 n=10)\nRoundBank/typical_price-10  3.214n ± 0%   2.883n ± 0%  -10.30% (p=0.000 n=10)\nCmp/typical_price-10        2.161n ± 0%   2.133n ± 1%   -1.32% (p=0.000 n=10)\ngeomean                     7.775n        7.101n        -8.67%\n```\n\nOn amd64 deployments also consider `GOAMD64=v3`: the BMI2/ADX instructions\nmaterially speed the `bits.Mul64`/`bits.Add64` carry chains that dominate the\nprimitives (arm64 needs no flag).\n\n## Build tags\n\n| Tag | Effect |\n| --- | --- |\n| `zerodecimal_prec9` | lowers the compile-time `DefaultPrec` to 9 fractional digits (nanos), trading fractional resolution for integer range in division results |\n| `zerodecimal_prec12` | lowers `DefaultPrec` to 12, matching alpacadecimal's fixed scale |\n| `zerodecimal_nostrcache` | compiles out the ~8 MB small-value string/`driver.Value` cache (−1000.00..+1000.00) built at init |\n\n`DefaultPrec` is a compile-time constant by design — never a mutable global —\nso precision checks fold into immediate compares.\n\nThe full test suites assume `DefaultPrec` = 19. Compile + `go vet` is the\nsupported verification level for the `zerodecimal_prec9` and\n`zerodecimal_prec12` configurations in v1.\n\n## How correctness is enforced\n\n- **Deterministic cross-check in the default suite**\n  ([crosscheck_test.go](crosscheck_test.go)): every arithmetic, comparison,\n  rounding, parsing, and formatting result is checked against\n  shopspring/decimal's unbounded big.Int arithmetic over an exhaustive\n  boundary-value pair sweep plus 30,000 fixed-seed boundary-biased random\n  pairs. The overflow oracle is *iff*: every `ErrOverflow` must be proven\n  exact (the true coefficient really is ≥ 2^128) and every fitting result\n  must be returned — a spurious error fails as loudly as a wrong value.\n- **23 differential fuzz targets** ([fuzz_test.go](fuzz_test.go), `make\n  fuzz-all`): parse round trips and raw-string parsing, Add/Sub/Mul/Div/\n  QuoRem/Mod/Cmp with the same iff overflow proofs, all seven rounding modes\n  pinned to their shopspring equivalents, StringFixed, JSON/binary/SQL round\n  trips, garbage binary input (which must never panic), float conversion, and\n  a structural-invariant target. quagmt/udecimal serves as a second,\n  bit-compatible oracle for Add/Sub/Mul.\n- **6.5+ million fixed-seed primitive cases**: the u128/u256 primitives and\n  every reciprocal-division path are verified against `bits.Div64` and\n  `big.Int` at carry, limb, power-of-ten, and exact-overflow boundaries plus\n  millions of shaped random cases per run ([u128_test.go](u128_test.go),\n  [u256_test.go](u256_test.go), [div10_test.go](div10_test.go) — the loop\n  counts sum past 6.5 million), and the generated magic-constant tables are\n  recomputed from their definitions in [tables_test.go](tables_test.go).\n- **Codegen gates**: the inlining shape of the hot paths (what must inline,\n  what must stay outlined, cost ceilings against compiler drift) is asserted\n  from the compiler's own `-m=2` report in the default suite.\n\n## Limitations vs shopspring/decimal\n\n- **Bounded domain.** |value| \u003c 2^128 / 10^prec — at most 39 significant\n  digits and 19 fractional digits. There is no arbitrary-precision fallback;\n  out-of-domain results return `ErrOverflow`. shopspring is unbounded.\n- **`places` is `uint8`.** Negative places (rounding at tens/hundreds\n  positions, shopspring's `Round(-2)`) are unsupported by design — this is\n  what keeps the entire rounding family infallible.\n- **Division precision is compile-time.** `Div` truncates at adaptive\n  precision up to `DefaultPrec` (19, or 9/12 via build tags); there is no\n  runtime `DivisionPrecision` knob and no `DivRound`.\n- **No `Pow`, `Sqrt`, or transcendental functions yet.**\n- **Stricter parsing.** `\"1.\"` and `\".1\"` are rejected; shopspring accepts\n  both.\n- **No exotic float forms.** `NewFromFloat` rejects NaN/±Inf with\n  `ErrInvalidFloat` rather than panicking, and converts via the shortest\n  decimal representation (like shopspring) — floats outside the domain\n  error instead of rounding silently.\n\n## Acknowledgements\n\nAlmost all of zerodecimal is original — the zero-allocation `u128`/`u256`\nprimitives, reciprocal-multiply division by powers of ten, the SWAR parse\npath, and the width-dispatched 128/64 division are its own work. The one\nported component is float formatting:\n\n- `dbox.go` (shortest binary-to-decimal digit generation) is ported from the\n  Go standard library's Dragonbox implementation in `internal/strconv`,\n  BSD-3-Clause. The Dragonbox algorithm is by Junekey Jeon\n  (\u003chttps://github.com/jk-jeon/dragonbox\u003e). Full notice:\n  [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).\n\nshopspring/decimal and quagmt/udecimal serve as the correctness oracles; the\ncomparative benchmark harness additionally measures against jokruger/dec128,\nalpacadecimal, and ericlagergren/decimal.\n\n## License\n\n[MIT](LICENSE). zerodecimal incorporates BSD-3-Clause code from the Go\nstandard library; the required notice is reproduced in\n[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexandroskyriakakis%2Fzerodecimal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexandroskyriakakis%2Fzerodecimal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexandroskyriakakis%2Fzerodecimal/lists"}