Skip to content

fix(recovery): keep the charge trace's negative zero, and correct the skin-temp comment - #1446

Merged
ryanbr merged 3 commits into
mainfrom
fix/charge-trace-signed-zero
Aug 19, 2026
Merged

ryanbr merged 3 commits into
mainfrom
fix/charge-trace-signed-zero

Conversation

@ryanbr

@ryanbr ryanbr commented Aug 19, 2026

Copy link
Copy Markdown
Owner

Two residuals left behind by the rounding/formatting parity work in @bhelm's #1437 and #1440.
Both were found by re-checking those merges rather than by a report.

1. The near-zero case was still divergent (#1437)

#1437 correctly aligned the half-tie rule, but Math.round returns a Long, which has no negative
zero — so negating it before the division collapsed -0.0 to +0.0:

-Math.round(-scaled) / 100.0     // -(0L) / 100.0  ->  +0.0
-(Math.round(-scaled) / 100.0)   // -(0.0)         ->  -0.0

For any term in (-0.005, 0) — a value sitting just below baseline — Android printed z=0.0 where
Swift's .rounded() keeps -0.0 and prints z=-0.0. These go straight into the trace by string
interpolation, so the two platforms disagreed on the one line whose entire purpose is being
byte-identical.

Verified on a JVM that moving the paren changes nothing else: -0.125 → -0.13, -0.126 → -0.13,
0.125 → 0.13 all identical; only the near-zero negatives move.

Severity is low — this is diagnostic text, not a score, and #1437's own test already pins that the
traced score equals the plain scorer's. But byte-identical trace lines are exactly what that helper
exists for.

2. The #1440 comment describes a bug that does not exist

It claims Foundation renders negative zero as -+0.0. It does not — %+.1f of -0.0 is -0.0.

The real defect #1440 fixed was the hand-built sign it replaced: -0.0 >= 0 is true, so "+" was
prepended to a value that formatted as "-0.0", printing +-0.0. The code was correct; only the
explanation was wrong, and it would have misled whoever touched it next. Rewritten to state the actual
mechanism and why both zeroes are handled explicitly.

Verification

  • Mirrored regression fixtures on both platforms pin the near-zero term
  • Android 4,066 tests / 0 failures (--rerun-tasks, new test confirmed by name)
  • Both files are under Packages/ / Android, so swift-packages CI executes the Swift twin — no
    app-target Swift touched, so no app-build gate needed
  • i18n and doc-comment gates clean

ryanbr added 3 commits August 19, 2026 18:27
… skin-temp comment (#1437 follow-up)

Two residuals from the rounding/formatting parity work in #1437 and #1440.

#1437 aligned the half-tie rule but left the near-zero case divergent. Math.round returns a
Long, which has no negative zero, so negating it BEFORE the division collapsed -0.0 to +0.0:
for any term in (-0.005, 0) — a value just below baseline — Android printed `z=0.0` where
Swift's .rounded() keeps -0.0 and prints `z=-0.0`. These values are interpolated straight into
the trace, so the two platforms disagreed on the one line whose entire purpose is being
byte-identical. Parenthesising the division negates the Double instead; verified on a JVM that
every half-tie and ordinary value is unchanged (-0.125 → -0.13, 0.125 → 0.13) and only the
near-zero negatives move.

#1440's comment described a bug that does not exist: `%+.1f` renders -0.0 as "-0.0", not
"-+0.0". The real defect it fixed was the hand-built sign it replaced — `-0.0 >= 0` is TRUE,
so "+" was prepended to a value formatting as "-0.0", printing "+-0.0". The code was right;
the explanation would have misled the next reader.

Mirrored regression fixtures pin the near-zero term on both platforms. Android 4,066 tests /
0 failures; the Swift twin is a package test, so swift-packages CI executes it.
Re-review of my own fix: parenthesising the division closed only ONE of the two traps in this
helper.

`-0.0 < 0.0` is FALSE, so a sign COMPARISON cannot route an exact negative zero into the
negating branch at all — it takes the else branch and comes back +0.0. And -0.0 is reachable
here, not hypothetical: the skin-temp term is z = -|dev| / scale, so a deviation of exactly 0.0
(skin temp sitting on the personal baseline) produces -0.0, which Swift prints as z=-0.0.

Rounding the magnitude and reapplying the sign with copySign handles both traps at once: the
Long that has no negative zero, and the comparison that cannot see one. Verified on a JVM
across -0.0, small negatives, half-ties in both directions and positives.

Second mirrored vector added for the exact -0.0 case. Android 4,067 tests / 0 failures.
Third pass on my own comment. Correcting #1440's claim, I asserted that `%+.1f` cannot
distinguish the two zeroes for the "+0.0" case. That is also false: C printf and Java's
Formatter both render -0.0 as "-0.0" and 0.0 as "+0.0", checked directly. So the explicit
zero branch is redundant on the formatters we have actually verified.

The comment now says that, and gives the honest reason the branch stays: it pins the edge so the
two zeroes cannot drift from the Kotlin twin on a Foundation whose formatter has not been
checked. The bug the branch replaced is unchanged and correctly described - a sign built from
`dev >= 0`, which is TRUE for -0.0, prepending "+" to a value already formatting as "-0.0".
@ryanbr
ryanbr merged commit c593cba into main Aug 19, 2026
16 checks passed
@ryanbr
ryanbr deleted the fix/charge-trace-signed-zero branch August 19, 2026 07:03
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