OCaml 有类 C 的 round() 和 trunc() 函数吗?

Does OCaml have C-like round() and trunc() functions?

OCaml 的标准库包括几个等价于 C 的浮点函数,例如 mod_float 用于 C 的 fmod(),求幂运算符 ** 用于 C 的 pow(),以及其他函数,例如 ceillog

但是它是否也包括 round()trunc() 的等价物?有 truncate/int_of_float,但它们的类型是 float -> int 而不是 float -> float

如果你想要一个类型为 float -> float 的函数 truncate,你可以这样做,但它非常难看:

let mytruncate x = float_of_int (truncate x)

Core 库有一个包含许多此类函数的 Float 模块。

它包含modf函数,即瑞士刀函数,您可以使用它定义truncatefroundf函数:

# let truncatef x = snd (modf x);;
val truncatef : float -> float = <fun>
# truncatef 3.14;;
 - : float = 3.

round函数也可以用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.

但是可以用 floor

更简洁(和有效)地表达
# let roundf x = floor (x +. 0.5)

但是这两个舍入函数都有点问题,正如 core 实现中的评论所述:

(* 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.

所以实现舍入的更正确的方法是(来自核心库):

let round_nearest t =
  if t >= round_nearest_lb && t <= round_nearest_ub then
    floor (t +. 0.5)
  else
    t

但即使这样round_nearest也不是完美无缺的,例如:

# round_nearest 0.49999999999999994;;
- : float = 1.

这个 0.499999999999999940.5 的前身。 Pascal 的 blog 包含有关如何解决此问题的建议。以下应该在 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.
# 

这只是一种舍入策略,四舍五入到最近(直观的一种)。还有其他政策,它们有自己的警告。

关于截断,已经有人给你答案了:

let truncatef x = snd (modf x);

对于一轮,我建议: BatFloat.round_to_intBatFloat.round

查看 this 了解详情。