OCaml's standard library includes several floating-point functions equivalent to C ones, such as mod_float
for C's fmod()
, the exponentiation operator **
for C's pow()
, and other functions such as ceil
, log
, etc.
But does it also include equivalents for round()
and trunc()
? There is truncate
/int_of_float
, but their type is float -> int
and not float -> float
.
It contains the modf
function, that is a swiss-knife function, with which you can define the truncatef
and roundf
functions:
# let truncatef x = snd (modf x);;
val truncatef : float -> float = <fun>
# truncatef 3.14;;
- : float = 3.
The round
function also can be expressed with modf
# let roundf x = snd (modf (x +. copysign 0.5 x));;
val roundf : float -> float = <fun>
# roundf 3.14;;
- : float = 3.
# roundf 3.54;;
- : float = 4.
# roundf (~-.3.54);;
- : float = -4.
However it can be expressed more succinctly (and efficiently) with floor
# let roundf x = floor (x +. 0.5)
But both rounding functions are a little bit buggy, as comment in core
's implementation states:
(* Outside of the range [round_nearest_lb..round_nearest_ub], all representable doubles
are integers in the mathematical sense, and [round_nearest] should be identity.
However, for odd numbers with the absolute value between 2**52 and 2**53, the formula
[round_nearest x = floor (x + 0.5)] does not hold:
# let naive_round_nearest x = floor (x +. 0.5);;
# let x = 2. ** 52. +. 1.;;
val x : float = 4503599627370497.
# naive_round_nearest x;;
- : float = 4503599627370498.
*)
let round_nearest_lb = -.(2. ** 52.)
let round_nearest_ub = 2. ** 52.
So a little bit more correct way to implement the rounding would be (from Core library):
let round_nearest t =
if t >= round_nearest_lb && t <= round_nearest_ub then
floor (t +. 0.5)
else
t
But even this round_nearest
is not flawless, for example:
# round_nearest 0.49999999999999994;;
- : float = 1.
This 0.49999999999999994
is an immediate predecessor of 0.5
. Pascal's blog contains suggestions on how to solve this issue. The following should work in OCaml:
let round_nearest t =
if t >= round_nearest_lb && t <= round_nearest_ub then
floor (t +. 0.49999999999999994)
else
t
# round_nearest 0.49999999999999994;;
- : float = 0.
# round_nearest (~-.0.49999999999999994);;
- : float = 0.
# round_nearest (~-.1.49999999999999994);;
- : float = -1.
# round_nearest 0.5;;
- : float = 1.
# round_nearest ~-.0.5;;
- : float = -1.
#
And this is only one rounding policy, round to nearest (the intuitive one). There are other policies, that have their own caveats.
The Core
library has a Float module with many such functions.
For truncate, someone gave you an answer already:
let truncatef x = snd (modf x);
For round, I suggest:
BatFloat.round_to_int
or BatFloat.round
Check this for details.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With