From 6e0b6d02dbe238e6e3d675b51e44b2ac798ddd20 Mon Sep 17 00:00:00 2001 From: David Steinberg Date: Fri, 2 Jan 2015 12:39:22 +0000 Subject: [PATCH] py: Fix float to int conversion for large exponents. --- py/mpz.c | 82 ++++++++++++++++++++++++++++++++++++++++ py/mpz.h | 6 +++ py/objint_mpz.c | 6 +-- tests/float/float2int.py | 22 +++++++++-- 4 files changed, 109 insertions(+), 7 deletions(-) diff --git a/py/mpz.c b/py/mpz.c index a25ba9e7d2..b1d6e2b322 100644 --- a/py/mpz.c +++ b/py/mpz.c @@ -590,6 +590,14 @@ mpz_t *mpz_from_ll(long long val, bool is_signed) { return z; } +#if MICROPY_PY_BUILTINS_FLOAT +mpz_t *mpz_from_float(mp_float_t val) { + mpz_t *z = mpz_zero(); + mpz_set_from_float(z, val); + return z; +} +#endif + mpz_t *mpz_from_str(const char *str, mp_uint_t len, bool neg, mp_uint_t base) { mpz_t *z = mpz_zero(); mpz_set_from_str(z, str, len, neg, base); @@ -682,6 +690,80 @@ void mpz_set_from_ll(mpz_t *z, long long val, bool is_signed) { } } +#if MICROPY_PY_BUILTINS_FLOAT +void mpz_set_from_float(mpz_t *z, mp_float_t src) { +#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE +#define EXP_SZ 11 +#define FRC_SZ 52 +typedef uint64_t mp_float_int_t; +#else +#define EXP_SZ 8 +#define FRC_SZ 23 +typedef uint32_t mp_float_int_t; +#endif + union { + mp_float_t f; + struct { mp_float_int_t frc:FRC_SZ, exp:EXP_SZ, sgn:1; } p; + } u = {src}; + + z->neg = u.p.sgn; + if (u.p.exp == 0) { + // value == 0 || value < 1 + mpz_init_zero(z); + } else if (u.p.exp == ((1 << EXP_SZ) - 1)) { + // inf or NaN +#if 0 + // TODO: this probably isn't the right place to throw an exception + if(u.p.frc == 0) + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_OverflowError, "cannot convert float infinity to integer")); + else + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "cannot convert float NaN to integer")); +#else + mpz_init_zero(z); +#endif + } else { + const int adj_exp = (int)u.p.exp - ((1 << (EXP_SZ - 1)) - 1); + if (adj_exp < 0) { + // value < 1 , truncates to 0 + mpz_init_zero(z); + } else if (adj_exp == 0) { + // 1 <= value < 2 , so truncates to 1 + mpz_init_from_int(z, 1); + } else { + // 2 <= value + const int dig_cnt = (adj_exp + 1 + (DIG_SIZE - 1)) / DIG_SIZE; + const unsigned int rem = adj_exp % DIG_SIZE; + int dig_ind, shft; + mp_float_int_t frc = u.p.frc | ((mp_float_int_t)1 << FRC_SZ); + + if (adj_exp < FRC_SZ) { + shft = 0; + dig_ind = 0; + frc >>= FRC_SZ - adj_exp; + } else { + shft = (rem - FRC_SZ) % DIG_SIZE; + dig_ind = (adj_exp - FRC_SZ) / DIG_SIZE; + } + mpz_need_dig(z, dig_cnt); + z->len = dig_cnt; + if (dig_ind != 0) { + memset(z->dig, 0, dig_ind * sizeof(mpz_dig_t)); + } + if (shft != 0) { + z->dig[dig_ind++] = (frc << shft) & DIG_MASK; + frc >>= DIG_SIZE - shft; + } + while (dig_ind != dig_cnt) { + z->dig[dig_ind++] = frc & DIG_MASK; + frc >>= DIG_SIZE; + } + } + } +} +#undef EXP_SZ +#undef FRC_SZ +#endif + // returns number of bytes from str that were processed mp_uint_t mpz_set_from_str(mpz_t *z, const char *str, mp_uint_t len, bool neg, mp_uint_t base) { assert(base < 36); diff --git a/py/mpz.h b/py/mpz.h index e1d71bf4ae..d2b6fad5c6 100644 --- a/py/mpz.h +++ b/py/mpz.h @@ -85,6 +85,9 @@ void mpz_deinit(mpz_t *z); mpz_t *mpz_zero(void); mpz_t *mpz_from_int(mp_int_t i); mpz_t *mpz_from_ll(long long i, bool is_signed); +#if MICROPY_PY_BUILTINS_FLOAT +mpz_t *mpz_from_float(mp_float_t i); +#endif mpz_t *mpz_from_str(const char *str, mp_uint_t len, bool neg, mp_uint_t base); void mpz_free(mpz_t *z); @@ -93,6 +96,9 @@ mpz_t *mpz_clone(const mpz_t *src); void mpz_set(mpz_t *dest, const mpz_t *src); void mpz_set_from_int(mpz_t *z, mp_int_t src); void mpz_set_from_ll(mpz_t *z, long long i, bool is_signed); +#if MICROPY_PY_BUILTINS_FLOAT +void mpz_set_from_float(mpz_t *z, mp_float_t src); +#endif mp_uint_t mpz_set_from_str(mpz_t *z, const char *str, mp_uint_t len, bool neg, mp_uint_t base); bool mpz_is_zero(const mpz_t *z); diff --git a/py/objint_mpz.c b/py/objint_mpz.c index 554ec96579..23e3000235 100644 --- a/py/objint_mpz.c +++ b/py/objint_mpz.c @@ -298,9 +298,9 @@ mp_obj_t mp_obj_new_int_from_uint(mp_uint_t value) { #if MICROPY_PY_BUILTINS_FLOAT mp_obj_t mp_obj_new_int_from_float(mp_float_t val) { - // TODO: This doesn't handle numbers with large exponent - long long i = MICROPY_FLOAT_C_FUN(trunc)(val); - return mp_obj_new_int_from_ll(i); + mp_obj_int_t *o = mp_obj_int_new_mpz(); + mpz_set_from_float(&o->mpz, val); + return o; } #endif diff --git a/tests/float/float2int.py b/tests/float/float2int.py index ca2914c01a..b948755de5 100644 --- a/tests/float/float2int.py +++ b/tests/float/float2int.py @@ -1,10 +1,24 @@ # This case occurs with time.time() values print(int(1418774543.)) -# TODO: General case with large exponent -#print(int(2.**100)) +print(int(2.**100)) print("%d" % 1418774543.) -# TODO: General case with large exponent -#print("%d" % 2.**100) +print("%d" % 2.**100) + +testpass = True +for i in range(0,1024): + bitcnt = len(bin(int(2.**i))) - 3; + if i != bitcnt: + print('fail: 2**%u was %u bits long' % (i, bitcnt)); + testpass = False +print("power of 2 test: %s" % (testpass and 'passed' or 'failed')) + +testpass = True +for i in range(0,23): + digcnt = len(str(int(10.**i))) - 1; + if i != digcnt: + print('fail: 10**%u was %u digits long' % (i, digcnt)); + testpass = False +print("power of 10 test: %s" % (testpass and 'passed' or 'failed'))