How to use different libraries depending on the environment? How to use rationals?

I want to have a shared library between OCaml native and js_of_ocaml. The problem is that I am writing a software that will do financial calculations and I’d like to use decimal math (something like Zarith https://github.com/ocaml/Zarith) on the backend - where real calculations happen; whereas I can go perfectly fine with floats on the frontend.

What should I do in order to not have to rewrite this library two times?

Functors!

You can functorize your numerical piece of code so that it takes an abstract integer module and do the computation using that module.

module type INT = sig
  type t
  val (+) : t -> t -> t
 ....
end

module Make (I: INT) = struct
  open I
  (** Lot's of computations *)
end

You can look at funarith and alt-ergo as examples that do exactly this.

Note that js_of_ocaml provides a BigInt implementation, so you don’t have to use floats. We don’t have zarith.jsoo yet, though …

Thanks! I didn’t understand this example, but I’ll read about functors and come back for it, but regarding this:

Do you recommend that I implement a pure OCaml module for rational math using BigInt (reimplement portions of Zarith) or is there something easier that I’m missing?

Edit: Something like this maybe? Converting an arbitrary-precision rational number (OCaml, zarith) to an approximate floating number - Stack Overflow

(Num, BigInt, Ratio) are the equivalent of (Zarith, Z, Q), so rational numbers are available in both. Zarith is mostly better in every way (as long as you accept to depend on GMP).

Num is not pure OCaml, js_of_ocaml just provides js primitives to replace the C ones. In theory, this could be done for zarith as well, but nobody has done it yet.

I can’t seem to find this in jsoo docs.

Edit: By testing it seems like I should just accept it’s “the same” as the big_int module in Zarith and use it.

In the overview under the “Feature” header:

Num: supported using +nat.js option

I saw this, but didn’t find any API doc

Well, there isn’t any API. It’s exactly Num. js_of_ocaml simply provides javascript version of the C code, making it usable in js.

It seems that, since the Num library was split from the main distribution in OCaml 4.06, there is no longer an HTML version of the API online. However, the API for the version in OCaml 4.05 is still online here:

http://caml.inria.fr/pub/docs/manual-ocaml-4.05/libnum.html

and imagine it hasn’t changed since then.

2 Likes

Could someone please give a small concise example of using both Zarith and JSOO to do the following:

  1. Have two strings like “10.2” and “2.4”
  2. Subtract the latter from the former
  3. Print the result, which should look like “7.8”

Here it is for Zarith (using the REPL):

# #require "zarith";;
~/.opam/4.05.0/lib/zarith: added to search path
~/.opam/4.05.0/lib/zarith/zarith.cma: loaded
# #install_printer Q.pp_print;;
# let r = Q.( 102//10 - 24//10);;
val r : Q.t = 39/5
# Q.to_float r;;
- : float = 7.8

Note that there is no function to parse floating point values, you have to convert them to fractions yourself. There is Q.of_float but that may not give you what you want since the value is rounded to floating point precision:

# Q.of_float 10.2;;
- : Q.t = 2871044762448691/281474976710656
1 Like

Should I assume that 102//10 doesn’t get evaluated right away? And in general, what’s going on here?

Edit: Ah, found it - https://github.com/ocaml/Zarith/blob/master/q.mli#L267
I’m still a bit lost on how these overloadings happen when you import a module.

This was probably the single biggest thing that prevented me from “playing” with these numbers to see how they work.

1 Like

Ideally, this should be triggered automatically when the library is loaded but some work is needed.

1 Like

It is evaluated immediately but just returns a value Q.t (a rational number).

They are not properly speaking overloadings, the definitions of, here, Q shadow the existing ones when a local open is performed (Q.(e) is equivalent to let open Q in e, i.e., opening the module Q for the expression e).

Yeah, but if you use it outside of Q.( ), the old operator still works:


utop # Q.(102//10);;
- : Q.t = 51/5

utop # 102//10;;
- : float = 10.2

First note that the returned value is not the same, so both operators (Q.(//) and // are different). Moreover, // must be defined in a module that you opened because it is not a standard operator.

I get that they are different, what I don’t get is how the compiler knows which one of the two to use when inside the Q.( ), given OCaml’s eager nature.

Edit: Ah, you explained it above.

I should read more carefully.

This is so cool :sunny:

OK, here is what I have for the part where I need to convert strings to fractions, using Angstrom. I am not sure i have to use angstrom, but it works for me, as it’s either way part of my parser :

open Angstrom 

let is_digit = function
  | '0' .. '9' -> true
  | _ -> false

let integer =
  take_while is_digit
  >>= fun s ->
  return (`Int s)

let number =
  list [
    option (`Char '+') ((char '-') >>= fun c -> return (`Char c));
    integer;
    peek_char >>= function
      | None -> return (`Int "0")
      | Some '.' -> (char '.' *> integer)
      | Some _ -> return (`Int "0")
  ]
  >>= function
  | [`Char sign; `Int whole; `Int part] ->
    let  signed_whole = match sign with
      | '-' -> "-" ^ whole
      | '+' -> whole
      | _ -> assert false in
    let len_part = (String.length part) in
    let denom = (Int.pow 10 len_part) in
    return (`Number (int_of_string (signed_whole ^ part) , denom))
  | _ -> fail "Number parser shouldn't happen"

This gives me something like:

utop # Angstrom.parse_string Lklparser.number "-10.2";;
- : ([> `Number of int * int ], string) result = Result.Ok (`Number (-102, 10))

Of course, this doesn’t reduce the fraction, but Zarith doesn’t mind and does that for me:

utop # Q.(-102//10);;
- : Q.t = -51/5

My question now is why is something like this not part of Zarith itself, as this is quite hard to do for an OCaml newbie. Also, would something like this be a good pull request? I imagine not, as they wouldn’t want to include Angstrom as a dependency, and also probably my code is too naive for general usage…
But maybe something like this could be at least included in the docs?
Special shoutout to @Drup