Skip to content

fix(encoder): round in FloatPrecisionCodec instead of truncating - #27

Merged
Tom32i merged 1 commit into
feat/js-codec-cursorfrom
fix/float-precision-rounding
Oct 14, 2026
Merged

Tom32i merged 1 commit into
feat/js-codec-cursorfrom
fix/float-precision-rounding

Conversation

@Tom32i

@Tom32i Tom32i commented Oct 14, 2026

Copy link
Copy Markdown
Owner

Stacked on #26: the base is feat/js-codec-cursor, so this diff only shows the rounding fix. Merge #26 first, then this one, and both go out in v4.0.0.

Problem

FloatPrecisionCodec multiplies by 10^precision and hands the result to an integer codec, which truncates it. Floating-point multiplication is not exact, so values land just below the integer and lose one unit:

Sent value * 100 Encoded (before) Decoded (before)
0.29 28.999999999999996 28 0.28
0.57 56.99999999999999 56 0.56
-0.29 -28.999999999999996 -28 -0.28

JS (DataView.setInt8) and Go (T(c.factor*data)) both truncate.

Fix

Round to the nearest integer before encoding, on both sides:

  • JS: value < 0 ? -Math.round(-value) : Math.round(value). JS Math.round rounds halves up (-12.5 → -12), so the sign split makes halves round away from zero, like Go.
  • Go: T(math.Round(float64(c.factor * data))).

JS and Go now produce identical bytes. I checked 14 values: ±0.29, ±0.125 (exact halves), ±2.675, ±1.005, 123.455, ±0.015, and the int16 limits 327.67 / -327.68.

The wire format is unchanged. Only the values sent change: they're now rounded instead of truncated.

Performance

About 1.3 ns per encoded value (≈ 6.4 ns → 7.7 ns in a micro-benchmark, with the same call shape for every variant). The ternary was as fast as Math.sign(v) * Math.round(Math.abs(v)) and plain Math.round, and it's the only one of the three that matches Go.

Test plan

  • New JS and Go tests: ±0.29, ±0.57, ±0.125 → ±0.13, 1.27, -1.28. They fail before the fix (0.29 → 0.28) and pass after.
  • npm test: 93 passed. go test ./go/... passes.
  • npm run lint, go vet
  • JS vs Go bytes compared for 14 values: identical

data * factor is not exact in floating point: 0.29 * 100 is
28.999999999999996, which the int codec truncated to 28, so 0.29 decoded
as 0.28. Both JS and Go now round to the nearest integer, halves away
from zero, so a JS and a Go sender produce the same bytes.
@Tom32i
Tom32i marked this pull request as ready for review October 14, 2026 16:19
@Tom32i
Tom32i merged commit 9973805 into feat/js-codec-cursor Oct 14, 2026
@Tom32i
Tom32i deleted the fix/float-precision-rounding branch October 14, 2026 16:20
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