openlang

Same Int, two targets, two answers

Found this while running the same Gleam module on both targets, and it's a runtime fact more than a language one.

import gleam/int
import gleam/io

pub fn main() {
  let id = 9_007_199_254_740_993
  io.println(int.to_string(id))
}

gleam run prints 9007199254740993. gleam run --target javascript prints 9007199254740992. On the Erlang target an Int is a BEAM integer and grows as far as memory lets it. On JavaScript the compiler maps Int to a plain JS number, a double, so anything past 2^53 gets rounded to a neighbour. Same type in the source, same code, and the two runtimes quietly disagree. No error on either side.

The real value came from a JSON payload, the literal is just the smallest repro. How are people handling ints that have to mean the same thing on both targets?

后续讨论 3

顺着彼此的依据读下去
  1. 固定链接

    The literal is the easy case to spot. Multiply two ints that are each well inside the safe range and the product can cross 2^53 at run time. The JS target rounds it to the nearest double and returns. The Erlang target allocates a bigger integer and returns. Neither branch reports anything, so the only thing that notices is a test that runs on JavaScript. If the package claims both targets, gleam test --target javascript belongs next to the default run, and the test needs a value past the limit, not a small one.

  2. 固定链接

    For the per-target part, Gleam lets one function have an external for one target and a Gleam body for the rest. The body runs wherever no external matches.

    @external(javascript, "./int_ffi.mjs", "is_safe")
    pub fn is_safe(_n: Int) -> Bool {
      True
    }
    export function is_safe(n) {
      return Number.isSafeInteger(n);
    }

    On Erlang the body answers True, which is right there, since nothing is lost. On JavaScript Number.isSafeInteger gets the real number. It also catches a value that was already rounded, because a value past the limit never rounds back down into the safe range. The question is about the runtime, so it makes sense that the answer lives in the runtime's own file.

  3. 固定链接

    That worked, and it's behind an opaque type now so nobody can skip the check. pub opaque type Id with a from_int that returns Error(Nil) when is_safe says no. The JSON decoder for ids refuses on JavaScript instead of handing out the rounded neighbour, and Erlang never takes the error branch, which is exactly right.

同一个问题的其他角度

  1. D's pure lets a function write through its arguments

    D's pure is weaker than the word suggests, and I only noticed because an evaluator experiment wanted to cache calls. pure in…

    回复 4最近活动 d
  2. CTFE happens where the declaration asks for it

    Had D's CTFE pictured wrong for a while. I assumed that once a function is fit for compile time evaluation, the compiler woul…

    回复 4最近活动 d
  3. Braces around expr decide what the bytecode compiler sees

    Unbraced expr is the one Tcl habit I'd ban if I could! It looks like a style nit and it isn't. The braced line prints 0. The…

    回复 16最近活动 tcl