Skip to content

chain: fix IEEE-754 float noise in tool JSON output (#1) - #2

Merged
kiki830621 merged 1 commit into
mainfrom
idd/chain-1-round-lat-lon-in-executor-json-output-to
May 29, 2026
Merged

chain: fix IEEE-754 float noise in tool JSON output (#1)#2
kiki830621 merged 1 commit into
mainfrom
idd/chain-1-round-lat-lon-in-executor-json-output-to

Conversation

@kiki830621

@kiki830621 kiki830621 commented May 29, 2026

Copy link
Copy Markdown
Member

Single-root IDD chain for #1.

Root cause (corrected from the issue's premise)

The issue proposed rounding lat/lon to ~6 decimals. That is a no-op: 25.04 has no exact IEEE-754 Double representation, so (d * 1e6).rounded() / 1e6 lands on the same bit pattern and JSONSerialization still emits 25.039999999999999. The noise lives in the 17-significant-digit Double formatter, not the value — and it affects every non-exact Double this codebase re-serializes (coordinates, speeds, fares), not just lat/lon.

Fix

New JSONSanitize.clean(_:) recursively rewrites every Double to NSDecimalNumber(string: d.description) — the shortest string that round-trips — wired into all 8 sites that re-serialize codebase-constructed dicts (6 jsonResult helpers + 2 inline RailTools sites). Centralizing below the call sites also cleans coordinates riding inside payloads the issue didn't name (e.g. rail_search_stations matches).

Guarantees (empirically verified across the full Double domain):

  • Value-preserving — round-trips numerically equal; bit-exact for every finite value except -0.0 (renders 0; JSONSerialization already drops the sign on round-trip regardless).
  • Int/Bool untouched — no NSNumber/Float in the dicts (all reals are Double); native scalars don't match as Double (confirmed via NSNumber.objCType).
  • Non-finite pass-throughinf/nan rejected by JSONSerialization exactly as before.
  • Extreme-magnitude fallback — values beyond NSDecimalNumber's ±128 exponent ceiling fall back to raw formatting = no regression (never occur in transport data).
  • Only normal-value change: integer-valued doubles render 25 not 25.0 (identical JSON number, round-trips to 25.0).

Scope — raw TDX passthroughs correctly exempt

Four sites forward TDX's original response bytes verbatim (no deserialize→Double→reserialize round-trip), so they introduce none of our IEEE-754 noise and are intentionally NOT routed through the sanitizer (routing them would require parsing TDX JSON, re-introducing the very problem):

  • RailTools.swift:199 rail_find_trains
  • RailTools.swift:223 rail_status_train
  • RailTools.swift:251 rail_status_station
  • MaritimeTools.swift:103-106 maritime_status_schedule

Verification

  • TDD: flipped testBusStatusPositionsAssemblesPositions RED→GREEN (asserts clean "lat":25.04).
  • JSONSanitizeTests: numerically-exact round-trip across 13 adversarial values (incl. 1e-7, 1e300, Double.greatestFiniteMagnitude, -0.0), Int/Bool preservation, recursion, non-finite, strings/null.
  • Cross-module guard: no-noise + clean center-echo assertion in bike nearby.
  • Full suite GREEN: 111 tests, 2 keychain-skipped, 0 failures.
  • Independent adversarial review (2 reviewers, correctness + completeness lenses, each built & probed): both PASS, no blockers/majors. Surfaced minors (committed-test value set, doc precision, raw-passthrough disclosure) — all addressed in this revision.

Sister observation (out of scope, not chained): the 6 jsonResult helpers are byte-identical — a DRY follow-up could centralize them.

Refs #1

Chain stops at verified — run /idd-close #1 to close after review.

Tool output rendered non-exact Doubles via JSONSerialization's 17-digit
formatter, so coordinates like 25.04 leaked as 25.039999999999999. Rounding
the value cannot fix this — 25.04 has no exact IEEE-754 form, so the rounded
result is the same bit pattern; the noise lives in the formatter, not the value.

Route every serialized object through JSONSanitize.clean, which recursively
rewrites each Double to NSDecimalNumber(Double.description) — the shortest
string that round-trips bit-exactly. Int/Bool type tags are preserved and
inf/nan pass through unchanged (JSONSerialization rejects them as before).

Covers all 8 serialization sites (6 jsonResult helpers + 2 inline RailTools),
so coordinates inside any payload (incl. rail station matches) are cleaned, not
just the bus-position site named in the issue.

Refs #1
@kiki830621
kiki830621 force-pushed the idd/chain-1-round-lat-lon-in-executor-json-output-to branch from 0f90695 to 1ea4430 Compare May 29, 2026 11:57
@kiki830621
kiki830621 merged commit 695c0ef into main May 29, 2026
2 checks passed
@kiki830621
kiki830621 deleted the idd/chain-1-round-lat-lon-in-executor-json-output-to branch May 29, 2026 12:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant