Skip to content

RecoveryForecaster.clamp differs on signed-zero bounds across Swift and Kotlin #56

Closed

Description

@bhelm

Summary

The exact RecoveryForecast parity rollout found a deterministic IEEE-754 drift in RecoveryForecaster.clamp/3. The production implementations are unchanged; the rollout was halted without a fix.

Native reproduction

Run the same exact parity case through the Swift runner and then the Kotlin runner:

  1. clamp(+0.0, -0.0, 1.0)
    • Swift: 8000000000000000 (-0.0)
    • Kotlin: 0000000000000000 (+0.0)
  2. clamp(+0.0, -1.0, -0.0)
    • Swift: 0000000000000000 (+0.0)
    • Kotlin: 8000000000000000 (-0.0)

Mirror cases with x = -0.0 agree. The drift is specifically the signed-zero bound-selection behavior of Swift nested min/max versus Kotlin kotlin.math.min/max.

Relevant production paths

  • Packages/StrandAnalytics/Sources/StrandAnalytics/RecoveryForecast.swift — RecoveryForecaster.clamp
  • android/app/src/main/java/com/noop/analytics/RecoveryForecast.kt — RecoveryForecaster.clamp

Impact

clamp/3 is intended to be an exact cross-platform twin. For signed-zero boundary inputs, the two platforms return different IEEE bits. Any caller or parity contract that preserves/compares raw bits can observe the mismatch.

Acceptance

  • Add deterministic native signed-zero lower- and upper-bound cases.
  • Choose and document one cross-platform signed-zero contract.
  • Make both production implementations return identical IEEE bits for all four x = +/-0.0 boundary shapes.
  • Keep the fix scoped to this concrete drift and prove it red-before-fix, then green on both native runners.

Activity

  1. bhelm commented on Aug 19, 2026

    @bhelm
    OwnerAuthor

    Resolved and differentially verified.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions