Skip to content

Commit

Permalink
Merge pull request #534 from skirpichev/fix-mpz-__float__-533
Browse files Browse the repository at this point in the history
Fix mpz/mpq.__float__, add tests
  • Loading branch information
casevh authored Dec 10, 2024
2 parents 3609fb4 + f44c19b commit 94f455c
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 30 deletions.
104 changes: 80 additions & 24 deletions src/gmpy2_convert_gmp.c
Original file line number Diff line number Diff line change
Expand Up @@ -164,23 +164,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 *
Expand Down Expand Up @@ -799,22 +845,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 *
Expand Down
7 changes: 3 additions & 4 deletions src/gmpy2_convert_mpfr.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
2 changes: 0 additions & 2 deletions test/test_mpfr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
17 changes: 17 additions & 0 deletions test/test_mpq.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import math
import numbers
import pickle
import platform
import sys
from decimal import Decimal
from fractions import Fraction
Expand Down Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions test/test_mpz.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down

0 comments on commit 94f455c

Please sign in to comment.