如何在 SML 中将实数四舍五入到第 n 个小数?



SML的新功能,试图通过声明函数round(n,L(将实数四舍五入到第n位小数,其中L是实数列表,n决定可以四舍五入的第n个小数。

我的方法是先将实数转换为字符串,然后将子字符串转换为第 n 个小数,然后将子字符串解析回实数,如果我只想将实数转换为第 n 位小数,这很好用,但是如果我有一个像 0.3456 这样的数字,我想四舍五入到 0.35, 我的方法不会真正做到这一点。

fun rd(_,[]) = []
|rd(a:int, x::y:real list) =
if x>0.0
then Option.getOpt(Real.fromString(String.substring(Real.toString(x),0,a+2)),0.0) :: rd(a,y)
else Option.getOpt(Real.fromString(String.substring(Real.toString(x),0,a+3)),0.0) :: rd(a,y)

预期结果是这样的:

- rd (2, [0.1234, 0.2345, ~0.3456]);
val it = [0.12,0.23,~0.35] : real list`

但我得到的实际输出是

val it = [0.12,0.23,~0.34] : real list

如果我想四舍五入数字,有什么好方法吗?

我也试过这个:

fun rd(_,[]) = []
|rd(a:int, x::y:real list) =
let
val n = real(round(x*Math.pow(10.0,real(a)))) / Math.pow(10.0,real(a))
in n::rd(a,y)
end;

但是这个解决方案会给我一个未捕获的异常溢出......

试图将实数四舍五入到小数点的第n位

声明一个函数round(n,L),其中L是实数的列表,n决定第n个小数

从您在第二次尝试的解决方案中使用Math.pow(10.0,real(a))来看,您似乎走上了正轨。我不明白列表在哪里;正如 Yawar 指出的那样,尝试解决这个问题以舍入单个实数,然后将其递归地(使用map(应用于实数列表。

所以一个函数

fun roundN (x, n) = ...
fun roundManyN (xs, n) = map (fn x => roundN (x, n)) xs

首先制作一些示例并将它们编码为测试。由于您无法在这些测试中比较真实相等性,因此请首先创建(或复制(自定义相等运算符。

fun nearlyEqual (a, b, eps) =
let val absA = Real.abs a
val absB = Real.abs b
val diff = Real.abs (a - b)
in Real.== (a, b) orelse
( if Real.== (a, 0.0) orelse
Real.== (b, 0.0) orelse
diff < Real.minNormalPos
then diff < eps * Real.minNormalPos
else diff / Real.min (absA + absB, Real.maxFinite) < eps )
end
val test_roundN_1 =
let val got = roundN (3.14159, 1)
val expected = 3.1
in nearlyEqual (got, expected, 0.1) end
val test_roundN_2 =
let val got = roundN (3.14159, 2)
val expected = 3.14
in nearlyEqual (got, expected, 0.01) end
(* rounding point *)
val test_roundN_3 =
let val got = roundN (3.14159, 3)
val expected = 3.142
in nearlyEqual (got, expected, 0.001) end
(* rounding point *)
val test_roundN_4 =
let val got = roundN (3.14159, 4)
val expected = 3.1416
in nearlyEqual (got, expected, 0.0001) end
val test_roundN_5 =
let val got = roundN (3.14159, 5)
val expected = 3.14159
in nearlyEqual (got, expected, 0.00001) end

您还有一些最终想要处理的边缘情况:

  • n为零或负数时,或者当n大于分数中的位数时。
  • x接近舍入点时,例如roundN (3.1451, 2)~>3.15.
  • x·10ⁿ的幅度超过 int 的大小时。
  • n太大时,幅度变化可能会影响实数的精度。

为了获得更好的测试库,请查看 testlib.sml(及其在 test.sml 中的用法(在本练习中。

将第二个解决方案提取到函数中,并为Math.pow (10.0, real n)提供临时绑定,您将获得解决方案:

fun roundN (x, n) =
let val m = Math.pow(10.0, real n)
in real (round (x * m)) / m end

此解决方案将给我一个未捕获的异常溢出

关于什么输入,我可能会问。

一个来源可能是round : real -> int是一个偏函数:有些实数值不能表示为 int,例如Real.posInfReal.negInf1e10(在 32 位 SML 上(和1e19(在 64 位 SML 上(。为避免这种情况,请考虑使用Real.realRound : real -> real来避免 int 转换。

避免与x * Math.pow(10.0, real n)相关的错误导致不精确的一种方法可能是在乘法之前去除整数部分,并在除法后重新添加整数部分。

最新更新