Skip to content

int () truncates its result to 32 bits #911

Description

@CarstenGrohmann

int (x) compiles to float (x) | 0. JavaScript's | converts its left operand with ToInt32, so the result is always a signed 32 bit integer. Arguments outside -2**31 ... 2**31 - 1 come back wrong, multiples of 2**32 come back as 0.

Reproducer

values = [8589934588, 9007199254740988, 2147483648, 4294967296]
for v in values:
    print (v, '->', int (v))

CPython:

8589934588 -> 8589934588
9007199254740988 -> 9007199254740988
2147483648 -> 2147483648
4294967296 -> 4294967296

Transcrypt:

8589934588 -> -4
9007199254740988 -> -4
2147483648 -> -2147483648
4294967296 -> 0

Run with 3.7.16 on Python 3.7, compiled with -b -n -e 6 and executed under
node. Line 291 of __builtin__.js is identical on current master.

The failure is quiet. A memory size in kB, a file offset or a timestamp in microseconds passes 2**31 during normal operation and then yields a small or negative number instead of an error.

Testlet

For development/automated_tests/transcrypt/div_issues/__init__.py:

    autoTester.check ('Issue 911')  # int () truncated to 32 bits
    autoTester.check (int (2147483648))
    autoTester.check (int (4294967296))
    autoTester.check (int (8589934588))
    autoTester.check (int (9007199254740988))
    autoTester.check (int (-5.7), int (5.7))

Possible fix

export function int (any) {
    var f = float (any);
    return f === Infinity || f === -Infinity || isNaN (f) ? 0 : Math.trunc (f);
};

This makes int () exact over the whole range where a JavaScript number is exact, up to 2**53 - 1, and leaves truncation toward zero for negative floats unchanged. With the patch applied the reproducer above matches CPython.

Math.trunc is ES6. The default target is esv 6 and javascriptVersion gates nothing in the code generator, so no version switch is needed. int has no callers inside the runtime.

Note on NaN and Infinity

NaN | 0 and Infinity | 0 currently yield 0, where CPython raises ValueError and OverflowError. The version above keeps the 0 to stay minimal. Raising instead would match CPython but changes behaviour beyond the range fix, so it seems worth deciding separately.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions