Skip to content

JairusSW/zmij-as

Repository files navigation

╔══╗ ╔╗╔╗ ╦  ╦    ╔═╗ ╔═╗
 ╔╝  ║╚╝║ ║  ║ ══ ╠═╣ ╚═╗
╚══╝ ╩  ╩ ╩ ╚╝    ╩ ╩ ╚═╝

The fastest ECMAScript-Number.toString-compatible f64/f32 → string for AssemblyScript, built on the Żmij (Schubfach + xjb) core.

Table of Contents

Installation

npm install zmij-as

zmij-as is plain exported functions - no compiler transform required. The digit kernel uses SIMD when available and falls back to scalar SWAR otherwise.

To enable SIMD, add to your asc command:

--enable simd

Or in your asconfig.json:

{
  "options": {
    "enable": ["simd"]
  }
}

Usage

import { dtoa, dtoa_buffered, ftoa, ftoa_buffered } from "zmij-as";

dtoa(3.14159);     // "3.14159"
dtoa(1e21);        // "1e+21"
dtoa(-0.0);        // "0"
ftoa(0.1);         // "0.1"

Writing a serializer with its own buffer? Skip the String allocation and write UTF-16 straight into your buffer:

// UTF-16 written directly into your buffer (>= 64 bytes); no allocation.
const codeUnits = dtoa_buffered(buffer, value);

dtoa(x) matches V8 x.toString() / JSON.stringify(x) byte-for-byte - including Infinity, -Infinity, NaN, -0 → "0", 1e21 → "1e+21", 1e-7 → "1e-7", the fixed-vs-exponential thresholds, and the minimal-width signed exponent per ECMAScript Specification.

API

dtoa(value: f64): string                 // shortest round-trip, ECMAScript-formatted
dtoa_buffered(buffer, value: f64): u32   // write UTF-16 into buffer, return code-unit count
ftoa(value: f32): string                 // shortest round-trip, ECMAScript-formatted
ftoa_buffered(buffer, value: f32): u32   // write UTF-16 into buffer, return code-unit count

Buffer contract. dtoa_buffered / ftoa_buffered write the shortest decimal as UTF-16 directly into buffer (no intermediate ASCII pass or widening); buffer needs ≥ 64 bytes. The returned code-unit count is exact - the extra headroom covers the in-register digit-block stores, which can overshoot the logical end by up to one 8-char block.

Performance

dtoa (f64) latency vs the AssemblyScript stdlib, by input complexity

ftoa (f32) latency vs the AssemblyScript stdlib, by input complexity

dtoa (f64) per-stage latency breakdown

Charts are published per release to the docs branch via npm run charts:publish (scripts/publish-charts.sh) and pinned here by version.

Verification

dtoa/ftoa are checked against V8 ground truth (Number::toString for f64; the exact BigInt shortest-round-trip for f32) - 0 failures across every power of two (±1/±0xF ulp), every power of ten, tens of millions of random values, and a generated assertion suite with 100% line/branch coverage.

# generate the as-test spec from the V8 oracle, then run the suite
npm run gen-spec
npm test

# build the verify wasm + check against V8 (fixed edge + 2M random; pass a count)
npm run verify
node scripts/dtoa/verify.mjs 3000

# open-ended differential fuzz vs V8 (seeded, repro-friendly; --runs/--time/--seed)
npm run fuzz

Benchmarks

npm run bench -- --v8 --wavm
npm run charts:build -- --v8 --wavm
npm run charts:serve

Architecture

The shortest-decimal core is a port of Żmij (Schubfach + xjb single-power-of-ten multiply); the formatter lays out digits per the ECMA-262 Number::toString decision tree and stores UTF-16 directly.

Credits

The shortest-decimal digits are identical to Ryū/Dragonbox (shortest, round-to-nearest-even) - exactly what ECMA-262 mandates; only the surface formatting differs. The core is a port of Żmij by Victor Zverovich (MIT).

License

MIT

Contact

Please send all issues to GitHub Issues and to converse, please send me an email at me@jairus.dev

About

Port of the wonderful Żmij float-to-string algorithm written in AssemblyScript. Complies to the ECMA-262 specification

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors