Skip to content

Float constants never compare equal in the JS simplifier: x == 2.5 && x == 2.5 compiles to false #9099

Closed

Description

@cristianoc

Found by a bug hunt over the 16 commits from eb087382 to cd641cbc.

let a = (x: float) => x == 2.5 && x == 2.5
let b = (x: float) => x != 2.5 && x == 2.5
function a(x) {
  return false;
}

function b(x) {
  return x === 2.5;
}

a(2.5) returns false (expected true), and b(2.5) returns true (expected false).

An int constant and a float constant with the same value are also treated as different. In an untagged variant this sends a literal case to the wrong arm:

@unboxed type t = @as(1) L1 | N(float) | S(string)
let f = (v: t) => switch v { | N(1.0) => 4 | _ => 6 }

The compiler warns that 1 is always matched as L1, never as N, yet emits:

function f(v) {
  if (v !== 1.0) {
    return 6;
  } else {
    return 4;
  }
}

so f(L1) returns 4 at run time, where the warning says it should return 6. @as(2.5) L | N(float) with N(2.5) behaves the same way. All of these reproduce at b123b7c3 as well.

Cause

Js_analyzer.eq_expression returns false for any two Number (Float _), and compares Number (Int _) only with another Int. The rules in simplify_and_ take "not eq_expression" to mean "different constants". So x === c1 && x === c2 becomes false, and x !== c1 && x === c2 becomes x === c2. Under JS ===, equal float literals are equal, and so are 1 and 1.0.

🤖 Generated with Claude Code

Activity

  1. added theissue type on Oct 28, 2026
  2. added a commit that references this issue on Oct 28, 2026
    6cf78f6
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

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions