Exact rational numbers

I’m wondering how to fill this task:
http://rosettacode.org/wiki/Currency
Which library should I use?
I see that the C example uses GMP and that Zarith implementation is based on GMP, but when I try to get a result I’m only able to get a result like:
24769797950537734440147467139809 / 1125899906842624
instead of just:
22000000000000005.72 (the total price before tax)

Should I use another lib, or is there a printing/converting function in Zarith that I missed?

Edit:
I just tryed ytomino/gmp-ocaml but I get similar ratio results than with Zarith.

IIRC the internal representation is not guaranteed to be decimal-based. But there should be some function that will format a Bignum in decimal notation … I used the Bignum wrapper of Zarith (only b/c of sexp support) and while I can’t lay my finger on the specific function, I distinctly remember there being one. Of course, such a function would end up rounding at some point, since “infinite decimal expansion”.

There was also older discussion at Decimal floating point arithmetic.
Note, that I am not aware of any decimal number libraries for OCaml. Nor there is an implementation of posits (unums) in OCaml - see examples in other languages. Which I find surprising, considering OCaml is used by JaneStreet and Imandra.

You can wrap the external library, e.g. in C or Rust using OCaml Ctypes bindings, meanwhile.
There is a similar situation, just a bit better, in the Rust universe. This is why I suggested to create Business Applications Working Group in the Rust community. Maybe it makes sense to organize such a thing in OCaml universe as well?

2 Likes

I assume you want 1/3 to come out as 0.33, and 2/3 as 0.67? I.e., you want to round to the nearest cent. That’s easy to implement:

let to_cents (q : Q.t) : Z.t =
  let cents = Q.(q * (of_int 100)) in
  let whole_cents = Q.to_bigint cents in
  if Q.(geq (cents - of_bigint whole_cents) (1//2)) then
    Z.(whole_cents + one)
  else
    whole_cents

From there you can easily go to whole dollars and cents:

let to_dollars_and_cents (q : Q.t) : Z.t * Z.t =
  let cents = to_cents q in
  Z.(cents / (of_int 100), cents mod (of_int 100))

For example:

# #require "Zarith";;
# #install_printer Z.pp_print;;
# to_dollars_and_cents Q.(1//3);;
- : Z.t * Z.t = (0, 33)
# to_dollars_and_cents Q.(2//3);;
- : Z.t * Z.t = (0, 67)

EDIT: Note that if you just care about pretty printing, you can also just do

Q.(2//3 |> to_float |> Format.printf "%.2f\n")
1 Like

OK, I tryed to filled the task with your function, The first result r1 is correct, but the second and third results r2 and r3 are almost right, but there are small errors. I don’t know how to fix it.
Here is the code:

let to_cents (q : Q.t) : Z.t =
  let cents = Q.(q * (of_int 100)) in
  let whole_cents = Q.to_bigint cents in
  if Q.(geq (cents - of_bigint whole_cents) (1//2))
  then Z.(whole_cents + one)
  else whole_cents

let to_dollars_and_cents (q : Q.t) : Z.t * Z.t =
  let cents = to_cents q in
  Z.(cents / (of_int 100), cents mod (of_int 100))

let p1 = Q.mul (Q.of_string "4000000000000000") (Q.of_float 5.50) ;;
let p2 = Q.mul (Q.of_int 2) (Q.of_float 2.86) ;;

let r1 = Q.add p1 p2 ;;
let r2 = Q.mul r1 (Q.of_float (7.65 /. 100.)) ;;
let r3 = Q.add r1 r2 ;;

let my_to_string v =
  let d, c = to_dollars_and_cents v in
  Printf.sprintf "%s.%s" (Z.to_string d) (Z.to_string c) ;;

# my_to_string r1 ;;
- : string = "22000000000000005.72"
(* expected: "22000000000000005.72" OK *)

# my_to_string r2 ;;
- : string = "1683000000000000.41"
(* expected: "1683000000000000.44" *)

# my_to_string r3 ;;
- : string = "23683000000000006.13"
(* expected: "23683000000000006.16" *)

There’s also the Jane St. wrapper of Zarith, called “Bignum”, which has nice convenience functions:

# Bignum.(((of_int 5) / (of_int 7)) |> to_string_hum ~decimals:20);;
- : string = "0.71428571428571428571"

# Bignum.(((of_int 5) / (of_int 7)) |> round_decimal ~dir:`Nearest ~digits:4 |> to_string_hum ~decimals:20);;
- : string = "0.7143"

The first function formats while rounding. Apparently the rounding is “Nearest” and there’s no way to change that (but probably it’s what one wants). The second function just rounds, and you can control the direction, as well as how many digits of rounding. Also, as below, the rounding actually round the bignum so that a decimal representation is accurate.

# Bignum.(((of_int 5) / (of_int 7)) |> to_string_accurate);;
- : string = "(0.714285714 + 1/3500000000)"
# Bignum.(((of_int 5) / (of_int 7)) |> round_decimal ~dir:`Nearest ~digits:4 |> to_string_accurate);;
- : string = "0.7143"
2 Likes

With Bignum I can get the expected result :slight_smile:

Edit:
Just added the OCaml solution with Bignum:
http://rosettacode.org/wiki/Currency#OCaml

3 Likes

Glad you got it to work with Bignum.
In case you’re still curious what went wrong in your first attempt:

# Q.of_float 2.86;;
- : Q.t = 6440147467139809/2251799813685248
# Q.(286/100)
- : Q.t = 143/50

What’s happening here is that 2.86 cannot be represented precisely as a float.

3 Likes

I created also a feature request in Zarith library: https://github.com/ocaml/Zarith/issues/55