F # +. Net, ошибка вычисления с использованием функции System.Math.Floor - PullRequest
1 голос
/ 24 июня 2010

вот функция, которую я написал для печати каждой цифры числа с плавающей точкой в ​​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

Как видно из четвертой строки, fnum равно 3.00..., а значение floor равно 2.00..


Редактировать 3 - Решено

Спасибо всем, теперь я понимаю, в чем проблема.

Ответы [ 3 ]

3 голосов
/ 24 июня 2010

Это только одна из тех проблем округления, которые вы получаете с арифметикой с плавающей запятой.Вы найдете его представление 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 = ()

Конечно, вы можете придерживаться float, но добавьте очень маленькое значение допуска, чтобы эти углыслучаи всегда чуть выше ожидаемого значения, а не чуть ниже, например:

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)

, что дает тот же результат, что и выше.

1 голос
/ 24 июня 2010

Даже если вы получите 3.00000 ... с максимальной десятичной точностью, все равно может быть округление.Давайте не будем забывать, что значение хранится в двоичном формате, а для печати всегда требуется округление (от двоичного до десятичного).Ответ таков: хотя ваше значение печатается как 3.0000 ..., оно на самом деле меньше 3. Добавление большего количества десятичных цифр не приведет к его уменьшению.

Рассмотрим этот код:

> let num = 3.0 - 2.**(-51.);;
> printfn "%.30f" num;;
3.000000000000000000000000000000
> floor num;;
val it : float = 2.0
1 голос
/ 24 июня 2010

Предположительно значение fnum примерно равно 2.9999999999999999999, а printfn просто округляет его до 3,0 при отображении.Попробуйте увеличить количество отображаемых цифр, например, %.30f вместо %f.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...