OCaml是否有类似C语言的round()和trunc()函数?

6
OCaml的标准库包括几个与C语言等效的浮点数函数,例如mod_float对应C语言的fmod(),指数运算符**对应C语言的pow(),还有其他一些函数,如ceillog等。
但是它是否也包括round()trunc()的等效函数?有truncate/int_of_float,但它们的类型是float -> int而不是float -> float
4个回答

7

这段代码包含了一个万能函数modf,使用它可以定义出truncatefroundf函数:

# let truncatef x = snd (modf x);;
val truncatef : float -> float = <fun>
# truncatef 3.14;;
 - : float = 3.
< p > 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的博客中提出了解决此问题的建议。以下代码可用于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.
# 

这只是其中一种四舍五入的策略,即最近的整数(直观的方法)。还有其他的策略,它们也有自己的注意事项。


那是截断,不是四舍五入。 - sepp2k
1
http://blog.frama-c.com/index.php?post/2013/05/02/nearbyintf1展示了一个额外的值,这个值并不在Core的测试范围内:0.5的前驱。 - Pascal Cuoq

4

Core库有一个Float模块,其中包含许多这样的函数。


1
对于其他人在此时找到这个答案,Core的Float模块文档链接目前已经失效。Core及其文档正在不断变化,但目前,此页面是其中之一,展示了它提供的广泛的舍入函数集合。 - Mars

1

对于截断(truncate),已经有人给出了答案:

let truncatef x = snd (modf x);

我建议使用以下方法进行四舍五入: BatFloat.round_to_intBatFloat.round 详情请参见this

0
如果你想要一个类型为float -> float的截断函数,你可以这样做,但这非常丑陋:
let mytruncate x = float_of_int (truncate x)

3
如果数字超出整数范围,那么这种方法将无法奏效。 - sepp2k
实际上,在我的机器上进行了一些测试,truncate 无法超过2^61(在某些情况下甚至可以输出负值),对于超出其指定范围的更高值返回0。而“真正的” trunc 函数应该即使对于这些值也能正常工作。 - anol

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接