【问题标题】:F#+.Net, calculation error using the System.Math.Floor functionF#+.Net,使用 System.Math.Floor 函数计算错误
【发布时间】:2010-06-24 10:21:38
【问题描述】:

这是我编写的一个函数,用于打印 F# 中浮点数的每个数字:

let rec TestFloor (fnum:float) =
    let floor = System.Math.Floor(fnum)
    printfn "fnum:%f floor:%f" fnum floor
    if floor > 0.0 then TestFloor((fnum - floor) * 10.0)

反正结果很奇怪,例如:

> TestFloor 1.23;;
fnum:1.230000 floor:1.000000
fnum:2.300000 floor:2.000000
**fnum:3.000000 floor:2.000000**
fnum:10.000000 floor:9.000000
fnum:10.000000 floor:9.000000
fnum:10.000000 floor:9.000000
fnum:10.000000 floor:9.000000
fnum:10.000000 floor:9.000000
fnum:10.000000 floor:9.000000
fnum:10.000000 floor:9.000000
fnum:10.000000 floor:9.000000
fnum:9.999998 floor:9.000000
fnum:9.999982 floor:9.000000
fnum:9.999822 floor:9.000000
...

在第四行,3.0 的下限竟然是 2.0,这很奇怪。以下计算都出错了。我想知道那里发生了什么?

谢谢!


编辑 2

@sepp2k

这是使用 %.30f 后的结果:

> TestFloor 1.23;;
fnum:1.230000000000000000000000000000 floor:1.000000000000000000000000000000
fnum:2.300000000000000000000000000000 floor:2.000000000000000000000000000000
**fnum:3.000000000000000000000000000000 floor:2.000000000000000000000000000000**
fnum:9.999999999999980000000000000000 floor:9.000000000000000000000000000000
fnum:9.999999999999820000000000000000 floor:9.000000000000000000000000000000
fnum:9.999999999998220000000000000000 floor:9.000000000000000000000000000000
fnum:9.999999999982240000000000000000 floor:9.000000000000000000000000000000
fnum:9.999999999822360000000000000000 floor:9.000000000000000000000000000000

正如您在第四行看到的,fnum3.00...floor 的值是 2.00..


编辑 3 - 已解决

谢谢大家,现在我明白问题所在了。

【问题讨论】:

    标签: .net f#


    【解决方案1】:

    这只是浮点运算遇到的舍入问题之一。你会发现它的表示是 2.99999... 可能无限循环。对 printfn 的调用清楚地将其四舍五入为预期的表示,但 floor 仍将其视为 2.99999...

    这正是小数类型存在要解决的问题,所以如果我们改写成使用小数,我们会得到正确的结果:

    let rec TestFloor dnum =
        let fl = floor dnum
        printfn "fnum:%f floor:%f" dnum fl
        if fl > 0.0M then TestFloor((dnum - fl) * 10.0M)
    

    这给出了:

    > TestFloor 1.23M;;
    fnum:1.230000 floor:1.000000
    fnum:2.300000 floor:2.000000
    fnum:3.000000 floor:3.000000
    fnum:0.000000 floor:0.000000
    val it : unit = ()
    

    当然,你可以坚持浮动,但添加一个非常小的容差值,以确保这些极端情况总是略高于预期值,而不是略低于预期值,例如:

    let rec TestFloor fnum =
        let fl = floor (fnum + 0.00000000001)
        printfn "fnum:%f floor:%f" fnum fl
        if fl > 0.0 then TestFloor((fnum - fl) * 10.0)
    

    结果与上述相同。

    【讨论】:

    • 谢谢克里斯,现在我明白了如何以及为什么!
    【解决方案2】:

    大概fnum 的值类似于2.9999999999999999999printfn 只是在显示时将其四舍五入为3.0。尝试使用 %.30f 而不是 %f 来增加显示位数。

    【讨论】:

    • 好的,这是使用 %.30f 的结果: >TestFloor 1.23;; FNUM:1.230000000000000000000000000000地板:1.000000000000000000000000000000 FNUM:2.300000000000000000000000000000地板:2.000000000000000000000000000000 FNUM:3.000000000000000000000000000000地板:2.000000000000000000000000000000 ......你可以看到,当FNUM = 3.00 ...,地板是2.00 ... 跨度>
    • @Eric.By:那就试试更多的数字。
    【解决方案3】:

    即使您得到 3.00000... 在最大十进制精度下,仍可能存在四舍五入。我们不要忘记该值以二进制形式存储,并且出于打印目的(二进制到十进制)始终需要舍入。 答案是,虽然您的值打印为 3.0000...,但它实际上小于 3。添加更多的十进制数字不会减少它。

    考虑这段代码:

    > let num = 3.0 - 2.**(-51.);;
    > printfn "%.30f" num;;
    3.000000000000000000000000000000
    > floor num;;
    val it : float = 2.0
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-10-14
      • 2014-03-12
      • 1970-01-01
      • 1970-01-01
      • 2016-05-05
      • 1970-01-01
      • 2012-08-25
      • 1970-01-01
      相关资源
      最近更新 更多