From da5b84d401d4db8d95aa2e9cb0a644a3cbc1bbc4 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Wed, 22 Mar 2023 13:36:43 +0000 Subject: [PATCH 1/3] [mypyc] Fixes to float to int conversion Fix undefined behavior when converting a negative float to an unsigned integer type. Fix edge cases on 32-bit platforms. The first issue was caught by the testI32BasicOps test case, but only in some configurations. --- mypyc/lib-rt/int_ops.c | 4 ++-- mypyc/test-data/run-floats.test | 10 ++++++++++ test-data/unit/lib-stub/math.pyi | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/mypyc/lib-rt/int_ops.c b/mypyc/lib-rt/int_ops.c index 48583b056b83..897083a28ab7 100644 --- a/mypyc/lib-rt/int_ops.c +++ b/mypyc/lib-rt/int_ops.c @@ -294,8 +294,8 @@ PyObject *CPyLong_FromStr(PyObject *o) { } CPyTagged CPyTagged_FromFloat(double f) { - if (f < (double)CPY_TAGGED_MAX && f > CPY_TAGGED_MIN) { - return (CPyTagged)f << 1; + if (f < (CPY_TAGGED_MAX + 1.0) && f > (CPY_TAGGED_MIN - 1.0)) { + return (Py_ssize_t)f << 1; } PyObject *o = PyLong_FromDouble(f); if (o == NULL) diff --git a/mypyc/test-data/run-floats.test b/mypyc/test-data/run-floats.test index d84681ec658e..1e820106f3bb 100644 --- a/mypyc/test-data/run-floats.test +++ b/mypyc/test-data/run-floats.test @@ -185,6 +185,16 @@ def test_explicit_conversion_to_int() -> None: else: assert repr(int(x)) == repr(int_any(x)) + # Test some edge cases + assert 2**30 == int(2.0**30 + int()) + assert 2**30 - 1 == int(math.nextafter(2.0**30, 0)) + assert -2**30 - 1 == int(-2.0**30 - 1 + int()) + assert -2**30 == int(math.nextafter(-2.0**30 - 1, 0)) + assert 2**62 == int(2.0**62 + int()) + assert 2**62 == int(2.0**62 - 1 + int()) + assert -2**62 == int(-2.0**62 + int()) + assert -2**62 == int(-2.0**62 - 1 + int()) + def str_to_float(x: str) -> float: return float(x) diff --git a/test-data/unit/lib-stub/math.pyi b/test-data/unit/lib-stub/math.pyi index 85f3b3f169e1..587b04a56de8 100644 --- a/test-data/unit/lib-stub/math.pyi +++ b/test-data/unit/lib-stub/math.pyi @@ -13,3 +13,4 @@ def copysign(__x: float, __y: float) -> float: ... def isinf(__x: float) -> bool: ... def isnan(__x: float) -> bool: ... def isfinite(__x: float) -> bool: ... +def nextafter(__x: float, __y: float) -> float: ... From bf0385a837a9ac315393a8e48435eaa18ecd32b8 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Wed, 22 Mar 2023 14:26:33 +0000 Subject: [PATCH 2/3] Fix bad implicit coercion to double --- mypyc/lib-rt/int_ops.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/lib-rt/int_ops.c b/mypyc/lib-rt/int_ops.c index 897083a28ab7..843d9b0d2230 100644 --- a/mypyc/lib-rt/int_ops.c +++ b/mypyc/lib-rt/int_ops.c @@ -294,7 +294,7 @@ PyObject *CPyLong_FromStr(PyObject *o) { } CPyTagged CPyTagged_FromFloat(double f) { - if (f < (CPY_TAGGED_MAX + 1.0) && f > (CPY_TAGGED_MIN - 1.0)) { + if (f < ((double)CPY_TAGGED_MAX + 1.0) && f > (CPY_TAGGED_MIN - 1.0)) { return (Py_ssize_t)f << 1; } PyObject *o = PyLong_FromDouble(f); From ae7422f613c009a00e43cb976e0fd969438e14e6 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Wed, 22 Mar 2023 14:37:31 +0000 Subject: [PATCH 3/3] Python 3.8 and earlier don't have math.nextafter --- mypyc/test-data/run-floats.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypyc/test-data/run-floats.test b/mypyc/test-data/run-floats.test index 1e820106f3bb..2c101100549d 100644 --- a/mypyc/test-data/run-floats.test +++ b/mypyc/test-data/run-floats.test @@ -187,9 +187,9 @@ def test_explicit_conversion_to_int() -> None: # Test some edge cases assert 2**30 == int(2.0**30 + int()) - assert 2**30 - 1 == int(math.nextafter(2.0**30, 0)) + assert 2**30 - 1 == int(1073741823.9999999 + int()) # math.nextafter(2.0**30, 0)) assert -2**30 - 1 == int(-2.0**30 - 1 + int()) - assert -2**30 == int(math.nextafter(-2.0**30 - 1, 0)) + assert -2**30 == int(-1073741824.9999998 + int()) # math.nextafter(-2.0**30 - 1, 0) assert 2**62 == int(2.0**62 + int()) assert 2**62 == int(2.0**62 - 1 + int()) assert -2**62 == int(-2.0**62 + int())