Typed-struct float32 optimization can lose ≤1 ULP on non-canonical doubles

Offen
#3 0 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

Bewertung

Schwierigkeit
5/5
Geschätzter Aufwand
Über eine Woche
Anfängerfreundlichkeit
25/100
Issue-Typ
Bug
Klarheit
Muss geklärt werden
Aktivitätsstatus
Ruhig
Tech-Stack
javascript
Bereich
backend

Rechercherichtung

Beginne beim typed-struct randomAccessStructure number path und vergleiche ihn mit msgpackr v1's struct.js; führe anschließend die bereitgestellte JavaScript-Reproduktion aus. Done ist nicht definiert: Das issue verfolgt ausdrücklich eine zurückgestellte Änderung, und jede Implementierung würde eine abgestimmte Kompatibilitätsentscheidung sowie Tests für exakt gleiche Round-Trips und byte-identische Ausgabe erfordern.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Beschreibung

Summary

The typed-struct (randomAccessStructure) number path stores certain float64 values as a 4-byte float32 field and reconstructs them on decode using the significant-digit rounding heuristic (the mult10 table). For a double that is not the canonical shortest-decimal representative of its decimal value, decode returns the canonical neighbor instead — a loss of up to 1 ULP.

This is the deliberate msgpackr float32 optimization carried over byte-for-byte from msgpackr's struct.js (it is not a structon regression — structon's output is byte-identical to msgpackr v1.11's native randomAccessStructure here). Filing as a tracking issue per discussion; no fix planned right now.

Scope

  • Only the typed-struct path is affected. Plain msgpackr/cbor-x number encoding preserves the exact double.
  • The same behavior exists in msgpackr v1's struct.js (the deployed 5.0.x code), so any future change should be considered jointly.

Reproduction

import { Packr } from 'msgpackr';
import { createStructon } from 'structon';
const S = createStructon(Packr);
const enc = new S({ randomAccessStructure: true, useRecords: false });

const v = 1000.0001000000001;          // a double adjacent to the 1000.0001 literal
const out = enc.decode(enc.encode({ x: v })).x;
console.log(out);                       // 1000.0001
console.log(Object.is(out, v));         // false  (decoded to the canonical neighbor)
console.log(1000.0001 === v);           // false  (they are distinct doubles)

1000.0001 (the canonical literal) round-trips exactly; only the non-canonical neighbor canonicalizes.

Why it happens

In the number case, a float32-range value is written as float32, then accepted as num32 if number * mult10[exp] is an integer. That check passes for both a nice decimal and its ULP-adjacent neighbors, so the neighbor decodes to the nice-decimal double.

Possible fix (deferred)

Tighten the float32-acceptance check to require an exact round-trip (decode(float32-bytes) === number) before choosing num32, falling through to float64 otherwise. This would diverge from msgpackr v1's bytes (breaks the byte-identical-output property the test suite asserts), so it's a deliberate-optimization change, not a drop-in fix.

Impact

Low: ≤1 ULP (~1e-13 relative), only on the struct path, only for non-canonical doubles. Not a correctness concern for typical data (e.g. it did not contribute to the typed-struct memory blowup tracked separately).


Filed by Claude (Opus 4.7) on behalf of @kris-zyp during the typed-struct memory investigation.

Vorherrschende Sprache
JavaScript
Sterne
13
Forks
1
Ø Merge
4 T. 3 Std.
Gemergte PRs (30 T.)
2

Entwicklungsumgebung

Erste Schritte

  1. Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
  3. Forken Sie das Repository und arbeiten Sie in einem Branch.
  4. Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.

Ähnliche Issues

Weitere Issues zu JavaScript

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.