|
| 1 | +import pytest |
| 2 | +import pythonmonkey as pm |
| 3 | +import random |
| 4 | + |
| 5 | +def test_eval_numbers_bigints(): |
| 6 | + def test_bigint(py_number: int): |
| 7 | + js_number = pm.eval(f'{repr(py_number)}n') |
| 8 | + assert py_number == js_number |
| 9 | + |
| 10 | + test_bigint(0) |
| 11 | + test_bigint(1) |
| 12 | + test_bigint(-1) |
| 13 | + |
| 14 | + # CPython would reuse the objects for small ints in range [-5, 256] |
| 15 | + # Making sure we don't do any changes on them |
| 16 | + def test_cached_int_object(py_number): |
| 17 | + # type is still int |
| 18 | + assert type(py_number) == int |
| 19 | + assert type(py_number) != pm.bigint |
| 20 | + test_bigint(py_number) |
| 21 | + assert type(py_number) == int |
| 22 | + assert type(py_number) != pm.bigint |
| 23 | + # the value doesn't change |
| 24 | + # TODO (Tom Tang): Find a way to create a NEW int object with the same value, because int literals also reuse the cached int objects |
| 25 | + for _ in range(2): |
| 26 | + test_cached_int_object(0) # _PyLong_FromByteArray reuses the int 0 object, |
| 27 | + # see https://github.com/python/cpython/blob/3.9/Objects/longobject.c#L862 |
| 28 | + for i in range(10): |
| 29 | + test_cached_int_object(random.randint(-5, 256)) |
| 30 | + |
| 31 | + test_bigint(18014398509481984) # 2**54 |
| 32 | + test_bigint(-18014398509481984) # -2**54 |
| 33 | + test_bigint(18446744073709551615) # 2**64-1 |
| 34 | + test_bigint(18446744073709551616) # 2**64 |
| 35 | + test_bigint(-18446744073709551617) # -2**64-1 |
| 36 | + |
| 37 | + limit = 2037035976334486086268445688409378161051468393665936250636140449354381299763336706183397376 |
| 38 | + # = 2**300 |
| 39 | + for i in range(10): |
| 40 | + py_number = random.randint(-limit, limit) |
| 41 | + test_bigint(py_number) |
| 42 | + |
| 43 | + # TODO (Tom Tang): test -0 (negative zero) |
| 44 | + # There's no -0 in both Python int and JS BigInt, |
| 45 | + # but this could be possible in JS BigInt's internal representation as it uses a sign bit flag. |
| 46 | + # On the other hand, Python int uses `ob_size` 0 for 0, >0 for positive values, <0 for negative values |
| 47 | + |
| 48 | +def test_eval_boxed_numbers_bigints(): |
| 49 | + def test_boxed_bigint(py_number: int): |
| 50 | + # `BigInt()` can only be called without `new` |
| 51 | + # https://tc39.es/ecma262/#sec-bigint-constructor |
| 52 | + js_number = pm.eval(f'new Object({repr(py_number)}n)') |
| 53 | + assert py_number == js_number |
| 54 | + |
| 55 | + test_boxed_bigint(0) |
| 56 | + test_boxed_bigint(1) |
| 57 | + test_boxed_bigint(-1) |
| 58 | + |
| 59 | + limit = 2037035976334486086268445688409378161051468393665936250636140449354381299763336706183397376 |
| 60 | + # = 2**300 |
| 61 | + for i in range(10): |
| 62 | + py_number = random.randint(-limit, limit) |
| 63 | + test_boxed_bigint(py_number) |
| 64 | + |
| 65 | +def test_eval_functions_bigints(): |
| 66 | + ident = pm.eval("(a) => { return a }") |
| 67 | + add = pm.eval("(a, b) => { return a + b }") |
| 68 | + |
| 69 | + int1 = random.randint(-1000000,1000000) |
| 70 | + bigint1 = pm.bigint(int1) |
| 71 | + assert int1 == bigint1 |
| 72 | + |
| 73 | + # should return pm.bigint |
| 74 | + assert type(ident(bigint1)) == pm.bigint |
| 75 | + assert ident(bigint1) is not bigint1 |
| 76 | + # should return float (because JS number is float64) |
| 77 | + assert type(ident(int1)) == float |
| 78 | + assert ident(int1) == ident(bigint1) |
| 79 | + |
| 80 | + # should raise exception on ints > (2^53-1), or < -(2^53-1) |
| 81 | + def not_raise(num): |
| 82 | + ident(num) |
| 83 | + def should_raise(num): |
| 84 | + with pytest.raises(OverflowError, match="Use pythonmonkey.bigint instead"): |
| 85 | + ident(num) |
| 86 | + not_raise(9007199254740991) # 2**53-1, 0x433_FFFFFFFFFFFFF in float64 |
| 87 | + should_raise(9007199254740992) # 2**53, 0x434_0000000000000 in float64 |
| 88 | + should_raise(9007199254740993) # 2**53+1, NOT 0x434_0000000000001 (2**53+2) |
| 89 | + not_raise(-9007199254740991) # -(2**53-1) |
| 90 | + should_raise(-9007199254740992) # -(2**53) |
| 91 | + should_raise(-9007199254740993) # -(2**53+1) |
| 92 | + |
| 93 | + # should also raise exception on large integers (>=2**53) that can be exactly represented by a float64 |
| 94 | + # in our current implementation |
| 95 | + should_raise(9007199254740994) # 2**53+2, 0x434_0000000000001 in float64 |
| 96 | + should_raise(2**61+2**9) # 0x43C_0000000000001 in float64 |
| 97 | + |
| 98 | + # should raise "Use pythonmonkey.bigint" instead of `PyLong_AsLongLong`'s "OverflowError: int too big to convert" on ints larger than 64bits |
| 99 | + should_raise(2**65) |
| 100 | + should_raise(-2**65) |
| 101 | + not_raise(pm.bigint(2**65)) |
| 102 | + not_raise(pm.bigint(-2**65)) |
| 103 | + |
| 104 | + # should raise JS error when mixing a BigInt with a number in arithmetic operations |
| 105 | + def should_js_error(a, b): |
| 106 | + with pytest.raises(pm.SpiderMonkeyError, match="can't convert BigInt to number"): |
| 107 | + add(a, b) |
| 108 | + should_js_error(pm.bigint(0), 0) |
| 109 | + should_js_error(pm.bigint(1), 2) |
| 110 | + should_js_error(3, pm.bigint(4)) |
| 111 | + should_js_error(-5, pm.bigint(6)) |
| 112 | + |
| 113 | + assert add(pm.bigint(0), pm.bigint(0)) == 0 |
| 114 | + assert add(pm.bigint(1), pm.bigint(0)) == 1 |
| 115 | + assert add(pm.bigint(1), pm.bigint(2)) == 3 |
| 116 | + assert add(pm.bigint(-1), pm.bigint(1)) == 0 |
| 117 | + assert add(pm.bigint(2**60), pm.bigint(0)) == 1152921504606846976 |
| 118 | + assert add(pm.bigint(2**65), pm.bigint(-2**65-1)) == -1 |
| 119 | + |
| 120 | + # fuzztest |
| 121 | + limit = 2037035976334486086268445688409378161051468393665936250636140449354381299763336706183397376 # 2**300 |
| 122 | + for i in range(10): |
| 123 | + num1 = random.randint(-limit, limit) |
| 124 | + num2 = random.randint(-limit, limit) |
| 125 | + assert add(pm.bigint(num1), pm.bigint(num2)) == num1+num2 |
| 126 | + |
| 127 | +def test_eval_functions_bigint_factorial(): |
| 128 | + factorial = pm.eval("(num) => {let r = 1n; for(let i = 0n; i<num; i++){r *= num - i}; return r}") |
| 129 | + assert factorial(pm.bigint(1)) == 1 |
| 130 | + assert factorial(pm.bigint(18)) == 6402373705728000 |
| 131 | + assert factorial(pm.bigint(19)) == 121645100408832000 # > Number.MAX_SAFE_INTEGER |
| 132 | + assert factorial(pm.bigint(21)) == 51090942171709440000 # > 64 bit int |
| 133 | + assert factorial(pm.bigint(35)) == 10333147966386144929666651337523200000000 # > 128 bit |
| 134 | + |
| 135 | +def test_eval_functions_bigint_crc32(): |
| 136 | + crc_table_at = pm.eval(""" |
| 137 | + // translated from https://rosettacode.org/wiki/CRC-32#Python |
| 138 | + const crc_table = (function create_table() { |
| 139 | + const a = [] |
| 140 | + for (let i = 0n; i < 256n; i++) { |
| 141 | + let k = i |
| 142 | + for (let j = 0n; j < 8n; j++) { |
| 143 | + // must use bigint here as js number is trimmed to int32 in bitwise operations |
| 144 | + if (k & 1n) k ^= 0x1db710640n |
| 145 | + k >>= 1n |
| 146 | + } |
| 147 | + a.push(k) |
| 148 | + } |
| 149 | + return a |
| 150 | + })(); |
| 151 | + (n) => crc_table[n] |
| 152 | + """) |
| 153 | + assert type(crc_table_at(1)) == pm.bigint |
| 154 | + assert crc_table_at(0) == 0 |
| 155 | + assert crc_table_at(1) == 1996959894 |
| 156 | + assert crc_table_at(255) == 755167117 # last item |
0 commit comments