From f44c19bb8273e02005be7692f425e9bcd9590ff4 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 2 Dec 2024 17:19:17 +0300 Subject: [PATCH] Fix mpz/mpq.__float__, add tests Closes #533 --- src/gmpy2_convert_gmp.c | 104 ++++++++++++++++++++++++++++++--------- src/gmpy2_convert_mpfr.c | 7 ++- test/test_mpfr.py | 2 - test/test_mpq.py | 17 +++++++ test/test_mpz.py | 15 ++++++ 5 files changed, 115 insertions(+), 30 deletions(-) diff --git a/src/gmpy2_convert_gmp.c b/src/gmpy2_convert_gmp.c index fea5cd36..438912ab 100644 --- a/src/gmpy2_convert_gmp.c +++ b/src/gmpy2_convert_gmp.c @@ -159,23 +159,69 @@ GMPy_MPZ_Int_Slot(MPZ_Object *self) return GMPy_PyLong_From_MPZ(self, NULL); } +static CTXT_Object * +_get_ieee_context(long bitwidth) +{ + assert(bitwidth != 16 || bitwidth%32 == 0); + + CTXT_Object *result = (CTXT_Object*)GMPy_CTXT_New(); + + if (!result) { + return NULL; + } + if (bitwidth == 16) { + result->ctx.mpfr_prec = 11; + result->ctx.emax = 16; + } + else if (bitwidth == 32) { + result->ctx.mpfr_prec = 24; + result->ctx.emax = 128; + } + else if (bitwidth == 64) { + result->ctx.mpfr_prec = 53; + result->ctx.emax = 1024; + } + else if (bitwidth == 128) { + result->ctx.mpfr_prec = 113; + result->ctx.emax = 16384; + } + else { + double bitlog2 = floor((4 * log(bitwidth) / log(2.0)) + 0.5); + result->ctx.mpfr_prec = bitwidth - (long)(bitlog2) + 13; + result->ctx.emax = 1 << (bitwidth - result->ctx.mpfr_prec - 1); + } + result->ctx.subnormalize = 1; + result->ctx.emin = 4 - result->ctx.emax - result->ctx.mpfr_prec; + return (PyObject*)result; +} + static PyObject * -GMPy_PyFloat_From_MPZ(MPZ_Object *obj, CTXT_Object *context) +GMPy_PyFloat_From_MPZ(MPZ_Object *obj, CTXT_Object *unused) { - double res; - mpfr_t temp; - - mpfr_init2(temp, 53); - mpfr_set_z(temp, obj->z, MPFR_RNDN); - res = mpfr_get_d(temp, MPFR_RNDN); - mpfr_clear(temp); - - if (isinf(res)) { - OVERFLOW_ERROR("'mpz' too large to convert to float"); + MPFR_Object *tmp; + CTXT_Object *context = _get_ieee_context(64); + + CHECK_CONTEXT(context); + + if (!(tmp = GMPy_MPFR_New(53, context))) { + /* LCOV_EXCL_START */ return NULL; + /* LCOV_EXCL_STOP */ } - return PyFloat_FromDouble(res); + mpfr_clear_flags(); + tmp->rc = mpfr_set_z(tmp->f, obj->z, MPFR_RNDN); + GMPY_MPFR_CHECK_RANGE(tmp, context); + GMPY_MPFR_SUBNORMALIZE(tmp, context); + GMPY_MPFR_EXCEPTIONS(tmp, context); + + PyObject *res = GMPy_PyFloat_From_MPFR(tmp, context); + Py_DECREF(tmp); + Py_DECREF(context); + if (!res) { + OVERFLOW_ERROR("'mpz' too large to convert to float"); + } + return res; } static PyObject * @@ -789,22 +835,32 @@ GMPy_PyStr_From_MPQ(MPQ_Object *obj, int base, int option, CTXT_Object *context) } static PyObject * -GMPy_PyFloat_From_MPQ(MPQ_Object *obj, CTXT_Object *context) +GMPy_PyFloat_From_MPQ(MPQ_Object *obj, CTXT_Object *unused) { - double res; - mpfr_t temp; - - mpfr_init2(temp, 53); - mpfr_set_q(temp, obj->q, MPFR_RNDN); - res = mpfr_get_d(temp, MPFR_RNDN); - mpfr_clear(temp); - - if (isinf(res)) { - OVERFLOW_ERROR("'mpq' too large to convert to float"); + MPFR_Object *tmp; + CTXT_Object *context = _get_ieee_context(64); + + CHECK_CONTEXT(context); + + if (!(tmp = GMPy_MPFR_New(53, context))) { + /* LCOV_EXCL_START */ return NULL; + /* LCOV_EXCL_STOP */ } - return PyFloat_FromDouble(res); + mpfr_clear_flags(); + tmp->rc = mpfr_set_q(tmp->f, obj->q, MPFR_RNDN); + GMPY_MPFR_CHECK_RANGE(tmp, context); + GMPY_MPFR_SUBNORMALIZE(tmp, context); + GMPY_MPFR_EXCEPTIONS(tmp, context); + + PyObject *res = GMPy_PyFloat_From_MPFR(tmp, context); + Py_DECREF(tmp); + Py_DECREF(context); + if (!res) { + OVERFLOW_ERROR("'mpq' too large to convert to float"); + } + return res; } static PyObject * diff --git a/src/gmpy2_convert_mpfr.c b/src/gmpy2_convert_mpfr.c index 28c2990a..51fece0f 100644 --- a/src/gmpy2_convert_mpfr.c +++ b/src/gmpy2_convert_mpfr.c @@ -769,15 +769,14 @@ GMPy_MPFR_Int_Slot(MPFR_Object *self) { static PyObject * GMPy_PyFloat_From_MPFR(MPFR_Object *self, CTXT_Object *context) { - double res; - - res = mpfr_get_d(self->f, MPFR_RNDN); + CHECK_CONTEXT(context); + + double res = mpfr_get_d(self->f, GET_MPFR_ROUND(context)); if (isinf(res)) { OVERFLOW_ERROR("'mpfr' too large to convert to float"); return NULL; } - return PyFloat_FromDouble(res); } diff --git a/test/test_mpfr.py b/test/test_mpfr.py index 37a13bba..e931c695 100644 --- a/test/test_mpfr.py +++ b/test/test_mpfr.py @@ -108,9 +108,7 @@ def test_mpfr_conversion(): assert xmpz(mpfr(5.51)) == xmpz(6) pytest.raises(OverflowError, lambda: mpq(mpfr('inf'))) - pytest.raises(OverflowError, lambda: float(mpfr('inf'))) - pytest.raises(OverflowError, lambda: float(mpfr('1e+400'))) assert mpq(mpfr(4.5)) == mpq(9,2) diff --git a/test/test_mpq.py b/test/test_mpq.py index 71742570..fb5859fe 100644 --- a/test/test_mpq.py +++ b/test/test_mpq.py @@ -1,6 +1,7 @@ import math import numbers import pickle +import platform import sys from decimal import Decimal from fractions import Fraction @@ -42,6 +43,22 @@ def test_mpq_float(): assert float(mpq(9, 5)) == 1.8 +@pytest.mark.skipif(platform.machine() != "x86_64", + reason="XXX: fails in i686 manylinux images") +@settings(max_examples=10000) +@given(fractions()) +@example(Fraction(12143, 31517)) +def test_mpq_float_bulk(x): + q = mpq(x) + try: + fx = float(x) + except OverflowError: + with pytest.raises(OverflowError): + float(q) + else: + assert fx == float(q) + + def test_mpq_from_Decimal(): assert mpq(Decimal("5e-3")) == mpq(5, 1000) assert mpq(Decimal(1)) == mpq(1) # issue 327 diff --git a/test/test_mpz.py b/test/test_mpz.py index 5a331cba..2d8288bc 100644 --- a/test/test_mpz.py +++ b/test/test_mpz.py @@ -506,6 +506,21 @@ def test_mpz_round(): raises(TypeError, lambda: round(mpz(123456),'a',4)) +@settings(max_examples=10000) +@given(integers()) +@example(38732858750156021) +@example(225188150488381457) +def test_mpz_float_bulk(n): + m = mpz(n) + try: + fn = float(n) + except OverflowError: + with raises(OverflowError): + float(m) + else: + assert fn == float(m) + + def test_mpz_bool(): assert bool(mpz(100)) assert not bool(mpz(0))