Releases: vitaut/zmij
Release list
1.1 "fixed point release"
This release adds fixed notation support, along with substantial float and SIMD performance improvements, a smaller binary footprint via optional size optimizations, a standalone C header, broad portability fixes (32-bit, clang, GCC/NEON, MSVC), and a rewritten verification and benchmarking toolchain. Overall, double-to-string conversion is roughly 23-28% faster than in 1.0 on Apple M5 Max and 28-38% on AMD EPYC Milan.
Time per double on Apple M5 Max (smaller is better), from the dtoa-benchmark:
New Features & API
- Added support for fixed notation, which
writeautomatically emits (for example123.456rather than1.23456e+02) for values of moderate magnitude, switching to scientific notation for large or small magnitudes, similar to the default floating-point format in Python and other languages (#64, #111, #124, thanks @dougallj). - Added a dedicated C header (
zmij-c.h) (#93). - Extended
to_decimalwith a negative flag, which also enables correct handling of negative zero. - Added the
ZMIJ_OPTIMIZE_SIZEmacro that enables binary size optimizations when defined to 1 (the default when compiling with__OPTIMIZE_SIZE__), reducing zmij's code and data from ~20 KB to ~4 KB (~79% reduction on Apple clang at-O2). - Applied Dougall Johnson's
pow10compression method when compiling in binary-size-optimized mode (#97). - Added the
ZMIJ_USE_SIMDCMake option to control SIMD (#128, thanks @spinicist) and CMakeThreadssupport for the tooling (#122, thanks @spinicist).
Performance & SIMD
- Built the float string entirely inside a SIMD register for up to a ~30% speed-up (#140, thanks @TobiSchluter).
- Added SIMD for floats and optimized the float path more broadly (#120, thanks @TobiSchluter; addresses #99, #121).
- Assembled fixed-notation output in a single SIMD register on SSE4.1 for a ~6-8% speed-up on fixed-notation benchmarks (#137, thanks @TobiSchluter).
- Improved the SSE4.1 fixed-double path (#110, thanks @Antares0982) and the NEON fixed-double path (#114, thanks @Antares0982).
- Converted BCD → ASCII after shuffling for a ~3% speed-up (#112, thanks @TobiSchluter).
- Used addition instead of OR when inserting zeros to enable
LEA(#82, thanks @TobiSchluter). - Optimized
pow10table access to avoid keeping the index live acrossumul192. - Removed the Schubfach fallback for both the double and float paths.
- Applied a digit-split optimization from the xjb paper and extended it across the scalar, SSE, and NEON paths.
- Folded the leading-zero shift into the SIMD digit shuffle and reworked handling of the last digit to reduce branching.
- Made exponent output branch-free (idea by @xjb714) and optimized it for floats.
- Added an optional precomputed exponent table and micro-optimizations for GCC (e.g. forcing
cmov, replacing poorly predicted branches). - Landed additional SSE improvements and ensured scalars do not affect the SIMD memory layout (#125, #129, thanks @TobiSchluter).
- Derived length from the unshuffled value for the
x86-64-v2microarchitecture and reorganized the SSE code. - Forced SIMD constants to be loaded from memory rather than synthesized, improving code generation (#78, thanks @TobiSchluter).
- Re-enabled SIMD in the C version and fixed several SIMD configuration corner cases, including building the tests with
ZMIJ_USE_SIMD=0(#127).
Portability & Toolchain Fixes
- Fixed clang compile errors (#104, thanks @Antares0982) and GCC NEON build issues, including a build failure on Apple Silicon (#116).
- Fixed MSVC compilation of the C version and multiple C portability issues (#83, #84, #103, thanks @AlexGuteniev, @jcol2), including MSVC ARM64 and x86/x64 warning cleanups (#126, #136).
- Avoided a disallowed conversion from
unsignedtoint(#113, thanks @TobiSchluter) and renamed the C version'sselecttozmij_selectto avoid a clash with the POSIXselectfunction (#139, thanks @MasterDuke17). - Added a correct fallback for 32-bit systems and re-enabled SSE on 32-bit MSVC builds (#119, #135, fixes #132, thanks @IRainman).
- Guarded
__builtin_is_constant_evaluatedfor compilers such as VS2017 (#142, fixes #141, thanks @archo5). - Fixed various warnings in the C version (#87, #90, #92, #95, thanks @MasterDuke17, @IRainman, @himikof).
- Applied the correct test settings (#106, thanks @TobiSchluter) and removed unnecessary null termination for doubles (#107, thanks @AlexGuteniev).
Correctness & Safety
- Fixed a buffer underrun in the SSE fixed path (#86).
- Fixed incorrect
NaNformatting that produced-nan(#109). - Corrected handling of the last digit, including round-to-even behavior (#118).
Verification & Tooling
- Added a
double-to-string verification script (verify.py) that verifies zmij's rounding against the shortest correctly rounded representation for all significands of every binary exponent. - Reworked benchmarking:
- Switched to Google Benchmark and improved reporting.
- Added an A/B testing script (
abtest.py) withfloatsupport and a "benchmark all commits" workflow. - Added a fixed-notation benchmark (Canada dataset), a float benchmark, and a results-plotting script.
- Fixed the fixed-notation benchmark (#143, thanks @TobiSchluter).
- Added CI for Linux, macOS, and Windows, the last in #115 (thanks @Antares0982).
New Contributors
- @MasterDuke17: first contribution in #87
- @IRainman: first contribution in #90
- @himikof: first contribution in #95
- @jcol2: first contribution in #103
- @Antares0982: first contribution in #104
- @spinicist: first contribution in #122
- @archo5: first contribution in #142
Thanks also to @TobiSchluter, @AlexGuteniev, @dougallj, and @xjb714 for their continued contributions, and to everyone who reported issues.
Full Changelog: v1.0...v1.1
1.0 "exponentially fast"
What’s Changed
This release focuses on correctness and performance, providing a minimal, safe API to obtain the shortest correctly rounded decimal representation in either exponential format or as a decimal floating-point number.
Performance & Algorithm Improvements
- Optimized division, modulo, and logarithm computations
- Reduced conditional branching compared to Schubfach
- Simplified decimal significand selection by using a single shorter candidate, based on an idea by Cassio Neri
- Simplified and optimized modified rounding computation
- Applied an optimization by Yaoyuan Guo, replacing 2–3 costly 128×64 multiplications with a single multiplication in the common case
- Reworked digit generation to process eight digits at a time instead of using lookup tables, reducing branching and enabling better compiler optimization for more consistent performance (#4, thanks @TobiSchluter)
- Switched to BCD encoding to evaluate eight significand digits in parallel (#7, thanks @TobiSchluter and @xjb714)
- Made exponent handling branch-free (#10, #16, thanks @TobiSchluter)
- Switched to built-in leading-zero counting with a safe fallback for older compilers (#21, #22, #25, thanks @AlexGuteniev)
- Applied a collection of improvements from Dougall Johnson (#49):
- Further optimized division, modulo, and logarithm computations
- Optimized exponent output logic
- Generated the powers-of-10 table using
constexprand 192-bit arithmetic - Applied faster indexed loads on ARM to improve table access performance
- Introduced an optional precomputed
exp_shifttable to speed up decimal scaling
- Peeled off the rightmost digit to enable cheaper division and remove zero checks, reducing branching and streamlining digit extraction (#72, thanks @TobiSchluter)
- Replaced
abswith a ternary expression to recover ~10% performance on GCC (#66, thanks @TobiSchluter) - Reduced conditional branching on ARM to improve performance (#73, thanks @xjb714)
- Optimized NEON zero-check logic to speed up digit processing (#74, thanks @xjb714)
SIMD & Architecture Support
- Added an optimized
write_significandimplementation using NEON to accelerate digit extraction on supported platforms (thanks @dougallj) - Added SSE SIMD support on x86 to leverage 128-bit vector instructions for faster parallel digit processing and improved performance (#59, thanks @TobiSchluter)
- Enabled NEON vectorization on ARM64 MSVC (#55, thanks @AlexGuteniev)
- Disabled SIMD correctly when
ZMIJ_USE_SIMD=0(#75, thanks @TobiSchluter)
Portability & Toolchain Fixes
- Fixed MSVC support on x64 and ARM64 by improving code generation, replacing unavailable intrinsics, and resolving related warnings (#8, thanks @mmozeiko)
- Fixed multiple MSVC issues across 32-bit builds, table generation, warning cleanup, vector type handling, and forced inlining (#30, #31, #34, #42, #44, #48, #50, #65, #69, #71, #76, thanks @AlexGuteniev)
- Fixed compilation regressions after recent changes (#38, #45, #56, #57, #77, thanks @AlexGuteniev and @TobiSchluter)
- Fixed GCC compilation issues related to NEON intrinsics (#53)
API & Usability
- Added
to_decimalfor converting binary floating-point values to decimal (#6) - Added
float(binary32) support (#1, #15) - Returned the size of the resulting representation (#32, thanks @AlexGuteniev)
- Reduced
float_buffer_sizefrom 17 to 16 (#51, #52, thanks @dtolnay) - Trimmed leading zeros in float formatting (#27, thanks @dtolnay)
- Lowered minimum required standard to C++14 (#61, #62, thanks @AlexGuteniev)
Correctness & Safety
- Added assertion to
countl_zerofor non-zero input (#29, thanks @AlexGuteniev) - Completed fallback implementation for
bswap64(#23, thanks @AlexGuteniev) - Avoided subtracting unrelated pointers for small buffers (#36, thanks @AlexGuteniev)
- Prevented use of hundreds in float exponent path (#43, thanks @AlexGuteniev)
- Fixed handling of subnormals (#11, #17, #19)
- Fixed incorrect formatting of 32-bit
infinityandNaNvalues (#70)
Verification & Tooling
- Added test coverage for major configurations: default (C++), no SIMD, no builtins, and C
- Added verification programs
- Parallelized the verification program to run across multiple threads, dividing the test space by hardware concurrency to speed up exhaustive correctness checking (#26, thanks @dtolnay)
- Improved verification tooling:
- Increased concurrency support (#33)
- Made failures return non-zero exit codes (#37)
- Added buffer overrun tests (#46, #54)
(thanks @AlexGuteniev)
- Fixed CSV writer usage (#60, thanks @TobiSchluter)
New Contributors
- @TobiSchluter – first contribution in #4
- @mmozeiko – first contribution in #8
- @dtolnay – first contribution in #13
Full Changelog:
https://github.com/vitaut/zmij/commits/v1.0
