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

Aperta
#3 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
5/5
Tempo stimato
Più di una settimana
Idoneità per principianti
25/100
Tipo di issue
Bug
Chiarezza
Da chiarire
Stato di attività
Tranquilla
Stack tecnologico
javascript
Ambito
backend

Direzione di ricerca

Inizia dal number path di typed-struct randomAccessStructure e confrontalo con struct.js di msgpackr v1, quindi esegui la riproduzione JavaScript fornita. Done non è definito: l'issue tiene esplicitamente traccia di una modifica rinviata e qualsiasi implementazione richiederebbe una decisione di compatibilità concordata, oltre a test per round trip esatti e un output identico byte per byte.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

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.

Lingua principale
JavaScript
Stelle
13
Fork
1
Merge medio
4g 3h
PR unite (30g)
2

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Issue simili

Altre issue su JavaScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.