Skip to content

test(analytics): skip the negative half-tie on JVMs whose exp is fdlibm - #2876

Open
andynilson wants to merge 1 commit into
ryanbr:mainfrom
andynilson:test/recovery-drivers-arm64-half-tie
Open

andynilson wants to merge 1 commit into
ryanbr:mainfrom
andynilson:test/recovery-drivers-arm64-half-tie

Conversation

@andynilson

Copy link
Copy Markdown

What this PR does

Two RecoveryDriversTest cases fail on Apple Silicon and pass on CI's x86-64 runner, both with
expected:<-0.5> but was:<-0.4999999999999929>:

  • driverPointRoundingUsesNearestWithHalfTiesAwayFromZero
  • issue51NegativeHalfTieUsesDefaultArg8WithoutChangingScoreOrDriverFields

Several open PRs list them as failing the same way on unmodified main.

Cause. The tests (from #1441) tune HRV 29.99117725828923 so the HRV marginal lands on exactly
−0.5. That holds only when exp is correctly rounded at the two exponents logisticScore evaluates.
On arm64 HotSpot, Math.exp returns fdlibm's (StrictMath) values there, each one ulp off, in the
same direction:

Exponent Correctly rounded exp fdlibm (arm64 HotSpot)
-0.2995162348730591 (HRV 29.99117725828923) 0.7411766894026283 0.7411766894026282
-0.32000000000000006 (neutral, HRV 30.0) 0.7261490370736908 0.7261490370736909

The marginal comes out -0.4999999999999929 and rounds to 0 instead of −1. The rounding rule is
right; the Swift twin (Apple libm) gets exactly −0.5, and ChargeDriversTests passes on the same machine.

Change (test-only, Kotlin).

  • The exact negative-tie assertions now run after assumeThisJvmCanReachTheNegativeTie(). It skips
    them only where exp(x) == StrictMath.exp(x) at both exponents.
  • On CI's x86-64 HotSpot the same assertions pass today, which fdlibm's pair cannot produce, so the
    probe is false there and they stay strict.
  • In the first test the exact tie moves to the end, so the positive tie and the neighbouring
    non-ties still run before any skip.
  • Same Assume pattern as the existing analytics/agreement/*Test.kt skips.

Not changed.

  • Product code: switching RecoveryScorer to StrictMath.exp would make Kotlin disagree with
    Swift at points like this.
  • The Swift twin: it reaches the exact tie as it is.

Alternatives considered.

  • Re-tuning the input: a search of 3M doubles on each side found none that gives exactly −0.5
    under fdlibm, so constants alone can't fix it.
  • A platform-independent construction (neutral side at exp(0) = 1, full side in the middle of
    the band that yields exactly 49.5) would hold everywhere but changes both twins. It can replace
    this if preferred.

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Documentation
  • CI / tooling

How it was tested

macOS 26 on Apple Silicon, Homebrew OpenJDK 17.0.21:

Run Before After
RecoveryDriversTest 17 run, 2 failed 17 run, 0 failed, 2 skipped
testFullDebugUnitTest --no-build-cache --rerun-tasks 6752 run, 2 failed, 6 skipped 6752 run, 0 failed, 8 skipped
Swift ChargeDriversTests (unchanged) 19 passed 19 passed
  • The cause was checked numerically:
    • a standalone Java replica of the scorer arithmetic reproduces -0.4999999999999929
    • 60-digit exp rounded to double gives the "correctly rounded" column, and with those values the marginal is exactly −0.5
    • Math.exp equals StrictMath.exp at both exponents on this JVM
  • doc_comment_lint.py OK; i18n_audit.py --ci upstream/main OK; no Kotlin warnings in the changed file.
  • Not run here: x86-64. The Android CI job is the check that the probe stays false there.

Checklist

  • Swift package tests pass for any package I touched (swift test in Packages/<name>) — n/a, no package touched (the Swift twin was run anyway: 19 passed)
  • Android unit tests pass if I touched android/ (./gradlew testFullDebugUnitTest; after a branch switch add --no-build-cache --rerun-tasks, or Gradle may fail classes you never touched)
  • No new build warnings introduced
  • UI changes use only StrandDesign tokens — no hardcoded colors, fonts, or spacing (no UI change)
  • No hardcoded hex frame bytes; protocol facts live in the schema / decoders
  • Follows the conventions in docs/CONTRIBUTING.md
  • I did not commit generated output (Strand.xcodeproj/) or any secrets/keystores

Related issues

Refs #1441 (where these tests were added). Reported as pre-existing in #2795, #2822, #2824, #2827, #2828.

RecoveryDriversTest pins the shared "nearest, half-ties away from zero"
rule with inputs tuned so the HRV marginal lands on exactly -0.5. That tie
only holds when exp is correctly rounded at the two exponents behind it.
HotSpot on arm64 (Apple Silicon) returns fdlibm's values there, one ulp
off at both in the same direction, so the marginal comes out
-0.4999999999999929 and rounds to 0. The two tests fail there (seen on
Apple Silicon) while passing on CI's x86-64 runner.

The rounding rule is not at fault, and the Swift twin (Apple libm)
reaches the exact tie, so the product code stays as it is. The exact
negative-tie assertions now run behind a probe that is true only where
Math.exp equals StrictMath.exp at both exponents. CI cannot take that
branch: the same assertions pass there, which fdlibm's values could not
produce. Everything the tie does not decide still runs first, and a
skipped tie reads as skipped rather than passed.
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