Is `int` still limited to [-2147483648, 2147483647]?

While trying to help a little with [Help needed] ReScriptifying Belt / Js API (sync back docs) - #2 by ryyppy I was shuffling docs of Js.Math module (Js.Math | ReScript API).

We can see there a few groups of functions working over floats and ints arguments that native JS does not distinguish. For example:


unsafe_floor_int

let unsafe_floor_int: float => int

Returns the largest integer less than or equal to the argument. This function may return values not representable by int, whose range is -2147483648 to 2147483647. This is because, in JavaScript, there are only 64-bit floating point numbers, which can represent integers in the range ±(253-1) exactly. See Math.floor on MDN.

Js.Math.unsafe_floor_int(3.7) == 3
Js.Math.unsafe_floor_int(3.0) == 3
Js.Math.unsafe_floor_int(-3.7) == -4
Js.Math.unsafe_floor_int(1.0e15) // result is outside range of int datatype

floor_int

let floor_int: float => int

Returns the largest int less than or equal to the argument; the result is pinned to the range of the int data type: -2147483648 to 2147483647. See Math.floor on MDN.

Js.Math.floor_int(3.7) == 3
Js.Math.floor_int(3.0) == 3
Js.Math.floor_int(-3.1) == -4
Js.Math.floor_int(-1.0e15) == -2147483648
Js.Math.floor_int(1.0e15) == 2147483647

floor_float

let floor_float: float => float

Returns the largest integral value less than or equal to the argument. The result is a float and is not restricted to the int data type range. See Math.floor on MDN.

Js.Math.floor_float(3.7) == 3.0
Js.Math.floor_float(3.0) == 3.0
Js.Math.floor_float(-3.1) == -4.0
Js.Math.floor_float(2_150_000_000.3) == 2_150_000_000.0

:thinking: :thinking: :thinking:

What is this 2147483647 magic number? Yes it is 232 ÷ 2, that is, a 32-bit integer limit. And int’s underlying type is the 32-bit integer. Sounds reasonable? At the times when ReasonML could target both JS and metal, the caveat made much sense. But is it still valid for ReScript which targets JS only?

I mean, there’s no such thing as 32-bit integer in JS. Everything is a Number. Every numerical value is capable to represent an integer in the range ±(253-1) and one must to do some extra work to artificially limit results to ±(231-1). And this isn’t done. For example:

Js.log(Js.Math.unsafe_floor_int(1.0e15 +. 0.5))

works just fine despite the caution in docs, because it gets compiled to:

console.log(Math.floor(1.0e15 + 0.5));

which is fine for any JS engine.

image

So, given ReScript is targeting JS only and getting rid of OCaml pervasives and built-ins, I wonder, does it make sense to keep all these caveats around 232? Or we’d better make a statement that int is an integer data type holding a value in range ±(253-1).

Are there any dependencies expecting the 32-bit range still?

4 Likes

A bit of preface. The 32-bit limitation, which I used to totally accept a few years ago, caused me quite much hassle because I needed some data type to hold money value in my business table-top game (https://futureco.world/). Given the limit, if I’d use plain int to hold the balance, one will have a trouble if collect over two billions in capital. Pffff, two billions, poor guy… So I had to invent the wheel and introduce a new hand-crafted data type with its own representation over the wire and so on.

If I was told the limit is 53 bits, I’d not bother with all the conversion. I’m pretty sure there’re many other scenarios where 32-bit is quite limiting.

Need to be careful with money :slight_smile: That’s why Java has BigDecimal, .NET has decimal, and Python has decimal.Decimal class. Even JavaScript now has BigInt for high-precision integers, and there are libraries floating around for arbitrary-precision decimal arithmetic. In fact there’s a BigInt bindings PR sitting in the ReScript repo backlog since last December.

To answer your original question–it might make sense to change the range of int. But surely that would be a backward-incompatible change, and might break existing real-world applications in subtle and surprising ways. Seems a bit dangerous to me.

Representing ints in JS is tricky.

Internally there is an int 32 type but it isn’t accessible to the programmer, only via tricks like using bitwise operators. That is why in many compile-to-js languages you’ll see integers when they always do number|0 in the int functions.

In theory you could safely represent integers from 2^53-1…2^53-1, which is already pretty big:

int32 -> 2147483647
int53 -> 9007199254740992

But those integers wouldn’t be able to use bitwise operators, only int32s in JS can use those, so you would use that functionality.

In terms of rescript it could probably be doable to migrate the int type to use floats to increase the range to 2^53 and move the bitwise operators to a new module for int32s, but like yawaramin mentioned it would be a breaking change so it would need to be done with care and with a long timeline.

Some links:


Re: money, never represent currency/money with numbers with precision, it is a recipe for pain and disaster, there are plenty of libraries for this, like https://github.com/MikeMcl/decimal.js/ or the now included in the language BigInt - JavaScript | MDN

2 Likes

Good point. For some reason I was sure that the bitwise maths uses 64-bit integers in JS. Apparently it is not:

image

1 Like

Good point. For some reason I was sure that the bitwise maths uses 64-bit integers in JS. Apparently it is not:

That assumption bit me on an Advent of Code problem from a few years ago. :clown_face: