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.posInf
、Real.negInf
、1e10
(在 32 位 SML 上(和1e19
(在 64 位 SML 上(。为避免这种情况,请考虑使用Real.realRound : real -> real
来避免 int 转换。
避免与x * Math.pow(10.0, real n)
相关的错误导致不精确的一种方法可能是在乘法之前去除整数部分,并在除法后重新添加整数部分。