I use the Zarith library to do rational arithmetic of arbitrary precision. Suppose I have a rational number q type Qt , that is, the ratio of two large integers ( q is the module of the rational number of numbers of arbitrary accuracy Zarith). Sometimes I would like to print this number as a floating point number for readability, and sometimes I need to convert this floating point number for subsequent calculations without arbitrary precision. Is there a way to convert q to floating point to a certain level of accuracy?
The method of converting q to a floating point now has no guarantees and can create undefined floating point numbers ( Z is an integer module of arbitrary precision):
let to_float q = let n, d = num q, den q in (* check if d is zero and raise an error if it is *) let nf, df = Z.to_float n, Z.to_float d in nf /. df
Is there a better way to handle this, where can I get a floating point that most closely approximates any q ?
Edit
I quickly wrote Mark Dickinson's answer in OCaml if anyone is interested. It can (definitely) be improved and refined. I will edit if I do this, or if anyone has any improvement tips. But for now, this solved my problem!
let to_float q = let n, d = num q, den q in let n_sign = Z.sign n in let d_sign = Z.sign d in (* always >= 0 *) if d_sign = 0 then raise Division_by_zero; let n = Z.abs n in if n_sign = 0 then 0. else let shift = (Z.numbits n) - (Z.numbits d) - 55 in let is_subnormal = shift < -1076 in let shift = if is_subnormal then -1076 else shift in let d = if shift >= 0 then Z.shift_left d shift else d in let n = if shift < 0 then Z.shift_left n (-shift) else n in let quotient, remainder = Z.div_rem nd in let quotient = if (Z.compare remainder (Z.zero)) = 0 && Z.is_even quotient then Z.add Z.one quotient else quotient in let quotient = if not is_subnormal then quotient else let round_select = Z.to_int @@ Z.rem quotient @@ Z.of_int 8 in Z.add quotient [|Z.zero;Z.minus_one;Z.of_int (-2);Z.one;Z.zero ;Z.minus_one;Z.of_int 2;Z.one|].(round_select) in let unsigned_res = ldexp (Z.to_float quotient) shift in if n_sign = 1 then unsigned_res else -.unsigned_res
Now I will consider creating an interface for the GMP mpq_get_d function, but I'm not quite sure how to do this. The only way I can see is to convert q : Qt to a string and pass to:
int mpq_set_str (mpq_t rop, const char *str, int base)
Does anyone know how to pass rop to mpq_get_d in OCaml or get a link that describes how to do this? I looked through chapter 19 of the RWO and did not see such a situation.