Search code examples
ocaml

Saving a large integer in OCaml


I am trying to convert a large base-10 number to base-16 and then to hex. The error I am getting is OCaml is saying the integer is too big.

# let large_int = 11515195063862318899931685488813747395775516287289682636499965282714637259206269 ;;
Error: Integer literal exceeds the range of representable integers of type int

I tried using Int64 as well and a similar issue.

# let large_int = Int64.of_string "11515195063862318899931685488813747395775516287289682636499965282714637259206269";;
    Exception: Failure "Int64.of_string".

This is the larger code I am trying to make work. It works as expected with the example integer below.

(* convert from base10_to_base16 *)
let rec split_digits n =
  if n = 0 then [] else
    let (d, rest) = n mod 256, n / 256 in
    d :: split_digits rest


let decode_base10 message =
  let bytes_arr = List.rev (split_digits message) in
  let decoded_message = String.concat "" (List.map (fun x -> String.make 1 (Char.chr x)) bytes_arr) in
  decoded_message


let base10_message = 310400273487 (* base-10 number *)
let decoded_message = decode_base10 base10_message

Solution

  • You'll want to install the Zarith library for working with arbitrary precision numbers, since the native integer types cannot handle the size numbers you're asking them to.

    Your split_digits function is then pretty straightforward to implement, using functions like Z.div and Z.rem in place of int operations / and mod.

    # #require "zarith";;
    
    # let n = Z.of_string "11515195063862318899931685488813747395775516287289682636499965282714637259206269";;
    val n : Z.t = <abstr>
    
    # let rec split_digits n =
        if n = Z.zero then []
        else
          let z256 = Z.of_int 256 in
          Z.to_int (Z.rem n z256) :: split_digits (Z.div n z256);;
    val split_digits : Z.t -> int list = <fun>
    
    # split_digits n;;
    - : int list =
    [125; 110; 119; 48; 100; 95; 121; 52; 119; 95; 51; 104; 55; 95;
     108; 108; 52; 95; 54; 110; 49; 100; 48; 99; 110; 51; 123; 111;
     116; 112; 121; 114; 99]
    

    Of course, you may want to make your split_digits function tail-recursive to avoid any chance of a stack overflow on especially enormous numbers. Easily accomplished with tail_mod_cons.

    let[@tail_mod_cons] rec split_digits n =
      if n = Z.zero then []
      else
        let z256 = Z.of_int 256 in
        Z.to_int (Z.rem n z256) :: split_digits (Z.div n z256)
    

    Of course, the same might be accomplished by adapting split_digits to generate a sequence. I'll also make the 256 a parameter for added flexibility.

    # let rec split_digits sz n () =
        let open Z in
        if n = zero then Seq.Nil
        else
          let z_sz = of_int sz in
          Seq.Cons (
            to_int @@ rem n z_sz, 
            split_digits sz @@ div n z_sz
          );;
    val split_digits : Z.t -> int -> int Seq.t = <fun>
    
    # n
      |> split_digits 256 
      |> List.of_seq;;
    - : int list =
    [125; 110; 119; 48; 100; 95; 121; 52; 119; 95; 51; 104; 55; 95;
     108; 108; 52; 95; 54; 110; 49; 100; 48; 99; 110; 51; 123; 111;
     116; 112; 121; 114; 99]