2
votes

OCaml newbie here.

I am trying to figure out how to deal with integer overflow in OCaml when converting from float to int.

I was hoping to use try ... with ... or compare it to nan (since at actually has to return nan when float is too large?), but looks like it does not throw any errors. And even more surprisingly for very large floats int_of_float simply returns 0.

utop # 0 = int_of_float 9999999999999999999.0;;
- : bool = true
utop # int_of_float 9999999999999999999.0;;
- : int = 0

How do I handle float to int conversion properly? (and more generally int overflow?)

2
Just a clarification about your first question mark: when the manual says result is unspecified if the argument is nan or falls outside the range of representable integers, it actually means anything can happen, so nothing is surprising. It is precisely left unspecified because they do not want to commit to any particular behavior (which might prevent optimizations and changes in future versions).anol

2 Answers

1
votes

The following module will do the job.

(*
   This module defines the range of integers that can be represented
   interchangeably with the float or with int type.

   Note that this range depends on size of the int type, which depends
   on the compilation target.
*)

(*
   OCaml ints have 31 bits or 63 bits.
   OCaml floats are double-precision floats (IEEE-754 binary64).
*)
let int_range_min_float = max (-.2.**53.) (float min_int)
let int_range_max_float = min (2.**53.) (float max_int)

let exact_int_of_float x =
  if x >= int_range_min_float && x <= int_range_max_float then
    truncate x
  else
    invalid_arg "exact_int_of_float: out of range"

let int_range_min_int = exact_int_of_float int_range_min_float
let int_range_max_int = exact_int_of_float int_range_max_float

let exact_float_of_int x =
  if x >= int_range_min_int && x <= int_range_max_int then
    float x
  else
    invalid_arg "exact_float_of_int: out of range"

let test () =
  let imin, imax = int_range_min_int, int_range_max_int in
  let fmin, fmax = int_range_min_float, int_range_max_float in
  assert (exact_int_of_float 1. = 1);
  assert (exact_int_of_float (-1.) = -1);
  assert (fmin < 0.);
  assert (fmax > 0.);
  assert (imin < 0);
  assert (imax > 0);
  assert (float (truncate fmin) = fmin);
  assert (float (truncate fmax) = fmax);
  assert (truncate (float imin) = imin);
  assert (truncate (float imax) = imax);
  assert (float (imin + 1) = float imin +. 1.);
  assert (float (imin + 2) = float imin +. 2.);
  assert (float (imax - 1) = float imax -. 1.);
  assert (float (imax - 2) = float imax -. 2.)
2
votes

Indeed, OCaml's manual indicates that float_of_int's "result is unspecified if the argument is nan or falls outside the range of representable integers."

A possibility is to check beforehand whether your float will fit or not, and return an option or raise an exception, as in e.g.

let safe_int_of_float f =
  if classify_float f = FP_nan then None
  else if f >= float_of_int max_int then None
  else if f <= float_of_int min_int then None
  else Some (int_of_float f)