klisp

an open source interpreter for the Kernel Programming Language.
git clone http://git.hanabi.in/repos/klisp.git
Log | Files | Refs | README

commit b3ad5ba049fd99f9f193d9d0082271536eb61fb4
parent b9128cbee229e6b0534ebb42abebc03242501887
Author: Andres Navarro <canavarro82@gmail.com>
Date:   Wed,  4 May 2011 19:17:14 -0300

Added new floating point printing routine, derived from a well known paper by Steele & White.

Diffstat:
Msrc/kreal.c | 341++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 340 insertions(+), 1 deletion(-)

diff --git a/src/kreal.c b/src/kreal.c @@ -131,6 +131,260 @@ TValue kexact_to_inexact(klisp_State *K, TValue n) ** read/write interface */ +/* +** SOURCE NOTE: This is a more or less vanilla implementation of the algorithm +** described in "How to Print Floating-Point Numbers Accurately" by +** Guy L. Steele Jr. & John L. White. +*/ + +/* +** TODO add awareness of read rounding (e.g. problem with 1.0e23) +** TODO add exponent if too small or too big +*/ + +mp_result shift_2(klisp_State *K, Bigint *x, Bigint *n, Bigint *r) +{ + mp_small nv; + mp_result res = mp_int_to_int(n, &nv); + klisp_assert(res == MP_OK); + + if (nv >= 0) + return mp_int_mul_pow2(K, x, nv, r); + else + return mp_int_div_pow2(K, x, -nv, r, NULL); +} + +/* returns k, modifies all parameters (except f & p) */ +int32_t simple_fixup(klisp_State *K, Bigint *f, Bigint *p, Bigint *r, + Bigint *s, Bigint *mm, Bigint *mp) +{ + mp_result res; + Bigint tmpz, tmpz2; + Bigint *tmp = &tmpz; + Bigint *tmp2 = &tmpz2; + Bigint onez; + Bigint *one = &onez; + res = mp_int_init(tmp); + res = mp_int_init(tmp2); + res = mp_int_init_value(K, one, 1); + res = mp_int_sub(K, p, one, tmp); + res = shift_2(K, one, tmp, tmp); + + if (mp_int_compare(f, tmp) == 0) { + res = shift_2(K, mp, one, mp); + res = shift_2(K, r, one, r); + res = shift_2(K, s, one, s); + } + + int k = 0; + + /* tmp = ceiling (s/10), for while guard */ + res = mp_int_add_value(K, s, 9, tmp); + res = mp_int_div_value(K, tmp, 10, tmp, NULL); + + while(mp_int_compare(r, tmp) < 0) { + --k; + res = mp_int_mul_value(K, r, 10, r); + res = mp_int_mul_value(K, mm, 10, mm); + res = mp_int_mul_value(K, mp, 10, mp); + /* tmp = ceiling (s/10), for while guard */ + res = mp_int_add_value(K, s, 9, tmp); + res = mp_int_div_value(K, tmp, 10, tmp, NULL); + } + + /* tmp = 2r + mp; tmp2 = 2s */ + res = mp_int_mul_value(K, r, 2, tmp); + res = mp_int_add(K, tmp, mp, tmp); + res = mp_int_mul_value(K, s, 2, tmp2); + while(mp_int_compare(tmp, tmp2) >= 0) { + + res = mp_int_mul_value(K, s, 10, s); + ++k; + + /* tmp = 2r + mp; tmp2 = 2s */ + res = mp_int_mul_value(K, r, 2, tmp); + res = mp_int_add(K, tmp, mp, tmp); + res = mp_int_mul_value(K, s, 2, tmp2); + } + + mp_int_clear(K, tmp); + mp_int_clear(K, tmp2); + mp_int_clear(K, one); + return k; +} + +/* TEMP: for now upoint is passed indicating where the least significant + integer digit should be (10^0 position) */ +#define digit_pos(k_, upoint_) ((k_) + (upoint_)) + +bool dtoa(klisp_State *K, double d, char *buf, int32_t upoint, int32_t *out_h, + int32_t *out_k) +{ + assert(sizeof(mp_small) == 4); + mp_result res; + Bigint e, p, f; + + assert(d > 0.0); + + /* convert d to three bigints m: significand, e: exponent & p: precision */ + /* d = m^(e-p) & m < 2^p */ + int ie, ip; + double mantissa = frexp(d, &ie); + ip = 0; + + klisp_assert(mantissa * pow(2.0, ie) == d); + /* now 0.5 <= mantissa < 1 & mantissa * 2^expt = d */ +/* this could be a binary search, it could also be done reading the exponent + field of ieee754 directly... */ + while(mantissa != floor(mantissa)) { + mantissa *= 2.0; + ++ip; + } + + /* mantissa is int & < 2^ip (was < 1=2^0 and by induction...) */ + klisp_assert(mantissa * pow(2.0, ie - ip) == d); + /* mantissa is at most 53 bits long as an int, load it in two parts + to f */ + int64_t im = (int64_t) mantissa; + /* f */ + /* cant load 32 bits at a time, second param is signed!, + but we know it's positive so load 32 then 31 */ + res = mp_int_init_value(K, &f, (mp_small) (im >> 31)); + res = mp_int_mul_pow2(K, &f, 31, &f); + res = mp_int_add_value(K, &f, (mp_small) im & 0x7fffffff, &f); + + /* adjust f & p so that p is 53 TODO do in one step */ + while(ip < 53) { + ++ip; + res = mp_int_mul_value(K, &f, 2, &f); + } + + /* e */ + res = mp_int_init_value(K, &e, (mp_small) ie); + + /* p */ + res = mp_int_init_value(K, &p, (mp_small) ip); + + /* start of FPP^2 algorithm */ + Bigint r, s; + Bigint mp, mm; + Bigint e_p; + Bigint zero, one; + + res = mp_int_init_value(K, &zero, 0); + res = mp_int_init_value(K, &one, 1); + + res = mp_int_init(&r); + res = mp_int_init(&s); + res = mp_int_init(&mm); + res = mp_int_init(&mp); + res = mp_int_init(&e_p); + + res = mp_int_sub(K, &e, &p, &e_p); + +// shift_2(f, max(e-p, 0), r); +// shift_2(1, max(-(e-p), 0), r); + if (mp_int_compare_zero(&e_p) >= 0) { + res = shift_2(K, &f, &e_p, &r); + res = shift_2(K, &one, &zero, &s); /* nop */ + res = shift_2(K, &one, &e_p, &mm); + } else { + res = shift_2(K, &f, &zero, &r); /* nop */ + res = mp_int_neg(K, &e_p, &e_p); + res = shift_2(K, &one, &e_p, &s); + res = shift_2(K, &one, &zero, &mm); + } + mp_int_copy(K, &mm, &mp); + + int32_t k = simple_fixup(K, &f, &p, &r, &s, &mm, &mp); + int32_t h = k-1; + + Bigint u, tmp, tmp2; + res = mp_int_init(&u); + res = mp_int_init(&tmp); + res = mp_int_init(&tmp2); + bool low, high; + + do { + --k; + res = mp_int_mul_value(K, &r, 10, &tmp); + res = mp_int_div(K, &tmp, &s, &u, &r); + res = mp_int_mul_value(K, &mm, 10, &mm); + res = mp_int_mul_value(K, &mp, 10, &mp); + + /* low/high flags */ + /* XXX try to make 1e23 round correctly, + it causes tmp == tmp2 but should probably + check oddness of digit and (may result in a digit + with value 10?, needing to backtrack) + In general make it so that if rounding done at reading + (should be round to even) is accounted for and the minimal + length number is generated */ + + res = mp_int_mul_value(K, &r, 2, &tmp); + + low = mp_int_compare(&tmp, &mm) < 0; + + res = mp_int_mul_value(K, &s, 2, &tmp2); + res = mp_int_sub(K, &tmp2, &mp, &tmp2); + + high = mp_int_compare(&tmp, &tmp2) > 0; + + if (!low && !high) { + mp_small digit; + res = mp_int_to_int(&u, &digit); + klisp_assert(res == MP_OK); + klisp_assert(digit >= 0 && digit <= 9); + buf[digit_pos(k, upoint)] = '0' + digit; + } + } while(!low && !high); + + mp_small digit; + res = mp_int_to_int(&u, &digit); + klisp_assert(res == MP_OK); + klisp_assert(digit >= 0 && digit <= 9); + + if (low && high) { + res = mp_int_mul_value(K, &r, 2, &tmp); + int cmp = mp_int_compare(&tmp, &s); + if ((cmp == 0 && (digit & 1) != 0) || cmp > 0) + ++digit; + } else if (low) { + /* nothing */ + } else if (high) { + ++digit; + } else { + assert(0); + } + /* double check in case there was an increment */ + klisp_assert(digit >= 0 && digit <= 9); + buf[digit_pos(k, upoint)] = '0' + digit; + + *out_h = h; + *out_k = k; + /* add '\0' to both sides */ + buf[digit_pos(k-1, upoint)] = '\0'; + buf[digit_pos(h+1, upoint)] = '\0'; + + mp_int_clear(K, &f); + mp_int_clear(K, &e); + mp_int_clear(K, &p); + mp_int_clear(K, &r); + mp_int_clear(K, &s); + mp_int_clear(K, &mp); + mp_int_clear(K, &mm); + mp_int_clear(K, &e_p); + mp_int_clear(K, &zero); + mp_int_clear(K, &one); + mp_int_clear(K, &u); + mp_int_clear(K, &tmp); + mp_int_clear(K, &tmp2); + + /* NOTE: digits are reversed! */ + return true; +} + + /* TEMP: this is a stub for now, always return sufficiently large number */ int32_t kdouble_print_size(TValue tv_double) @@ -142,6 +396,91 @@ int32_t kdouble_print_size(TValue tv_double) void kdouble_print_string(klisp_State *K, TValue tv_double, char *buf, int32_t limit) { - sprintf(buf, "%f", dvalue(tv_double)); + /* TODO: add exponent to values too large or too small */ + /* TEMP */ + int32_t h = 0; + int32_t k = 0; + int32_t upoint = limit/2; + double od = dvalue(tv_double); + klisp_assert(!isnan(od) && !isinf(od)); + klisp_assert(limit > 10); + + /* dtoa only works for d > 0 */ + if (od == 0.0) { + buf[0] = '0'; + buf[1] = '.'; + buf[2] = '0'; + buf[3] = '\0'; + return; + } + + double d; + if (od < 0.0) + d = -od; + else d = od; + + /* XXX this doesn't check limit, it should be large enough */ + UNUSED(dtoa(K, d, buf, upoint, &h, &k)); + + klisp_assert(upoint + k >= 0 && upoint + h + 1 < limit); + + /* rearrange the digits */ + /* TODO do this more efficiently */ + + + int32_t size = h - k + 1; + int32_t start = upoint+k; + /* first reverse the digits */ + for (int32_t i = upoint+k, j = upoint+h; i < j; i++, j--) { + char ch = buf[i]; + buf[i] = buf[j]; + buf[j] = ch; + } + + /* TODO use exponents */ + + /* if necessary make room for leading zeros and sign, + move all to the left */ + + int extra_size = (od < 0? 1 : 0) + (h < 0? 2 + (-h-1) : 0); + + klisp_assert(extra_size + size + 2 < limit); + memmove(buf+extra_size, buf+start, size); + + int32_t i = 0; + if (od < 0) + buf[i++] = '-'; + + if (h < 0) { + /* fraction with leading 0. and with possibly more leading zeros */ + buf[i++] = '0'; + buf[i++] = '.'; + for (int32_t j = -1; j > h; j--) { + buf[i++] = '0'; + } + int frac_size = size; + i += frac_size; + buf[i++] = '\0'; + } else if (k >= 0) { + /* integer with possibly trailing zeros */ + klisp_assert(size+extra_size+k+4 < limit); + int int_size = size; + i += int_size; + for (int32_t j = 0; j < k; j++) { + buf[i++] = '0'; + } + buf[i++] = '.'; + buf[i++] = '0'; + buf[i++] = '\0'; + } else { /* both integer and fractional part, make room for the point */ + /* k < 0, h >= 0 */ + int32_t int_size = h+1; + int32_t frac_size = -k; + memmove(buf+i+int_size+1, buf+i+int_size, frac_size); + i += int_size; + buf[i++] = '.'; + i += frac_size; + buf[i++] = '\0'; + } return; }