Вывести список с помощью итератора с плавающей точкой в ​​F # - PullRequest
2 голосов
/ 25 марта 2011

Рассмотрим следующий код:

let dl = 9.5 / 11.
let min = 21.5 + dl
let max = 40.5 - dl

let a = [ for z in min .. dl .. max -> z ] // should have 21 elements
let b = a.Length

"a" должен иметь 21 элемент, но имеет только 20 элементов.Значение "max - dl" отсутствует.Я понимаю, что числа с плавающей точкой не точны, но я надеялся, что F # сможет с этим работать.Если нет, то почему F # поддерживает списочные выражения с помощью итератора с плавающей точкой?Для меня это источник ошибок.

Онлайн-пробная версия: http://tryfs.net/snippets/snippet-3H

Ответы [ 4 ]

2 голосов
/ 25 марта 2011

При преобразовании в десятичные дроби и просмотре чисел кажется, что 21-й элемент мог бы «перескочить» максимум:

let dl = 9.5m / 11.m
let min = 21.5m + dl
let max = 40.5m - dl

let a = [ for z in min .. dl .. max -> z ] // should have 21 elements
let b = a.Length

let lastelement = List.nth a 19
let onemore = lastelement + dl
let overshoot = onemore - max

Это, вероятно, связано с отсутствием точности в let dl = 9.5m / 11.m?

Чтобы избавиться от этой составной ошибки, вам придется использовать другую систему счисления, то есть Rational.F # Powerpack поставляется с классом BigRational, который можно использовать следующим образом:

let dl = 95N / 110N
let min = 215N / 10N + dl
let max = 405N / 10N - dl

let a = [ for z in min .. dl .. max -> z ] // Has 21 elements
let b = a.Length
1 голос
/ 25 марта 2011

Правильное решение проблем точности с плавающей точкой может быть непростым делом.Вы не должны полагаться на равенство с плавающей точкой (это то, что понимание списка неявно делает для последнего элемента).Понимание списков на float полезно, когда вы генерируете бесконечный поток.В других случаях вам следует обратить внимание на последнее сравнение.

Если вам нужно фиксированное количество элементов и включить как нижнюю, так и верхнюю конечные точки, я предлагаю вам написать такую ​​функцию:

let range from to_ count =
    assert (count > 1)
    let count = count - 1
    [ for i = 0 to count do yield from + float i * (to_ - from) / float count]

range 21.5 40.5 21

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

let a = [ for z in min .. dl .. max + dl*0.5 -> z ]
0 голосов
/ 25 марта 2011

После запуска вашего кода, если вы делаете:

> compare a.[19] max;; 
val it : int = -1

Это означает, что max больше, чем a. [19]

Если мы выполняем вычисления таким же образом, как оператор диапазона, но группируем двумя различными способами, а затем сравниваем их:

> compare (21.5+dl+dl+dl+dl+dl+dl+dl+dl) ((21.5+dl)+(dl+dl+dl+dl+dl+dl+dl));;
val it : int = 0
> compare (21.5+dl+dl+dl+dl+dl+dl+dl+dl+dl) ((21.5+dl)+(dl+dl+dl+dl+dl+dl+dl+dl));;
val it : int = -1

В этом примере вы можете увидеть, как добавление 7 раз одного и того же значения в другом порядке приводит к абсолютно одинаковому значению, но если мы попробуем это 8 раз, результат изменится в зависимости от группировки.

Вы делаете это 20 раз.

Так что, если вы используете оператор диапазона с плавающей точкой, вы должны знать о проблеме точности. Но то же самое относится и к любым другим вычислениям с плавающей точкой.

0 голосов
/ 25 марта 2011

Я подозреваю, что проблема заключается в точности значений с плавающей запятой.F # добавляет dl к текущему значению каждый раз и проверяет, является ли current <= max.Из-за проблем с точностью, он может перепрыгнуть через max, а затем проверить, если max + ε <= max (что приведет к false).И поэтому в результате будет только 20 предметов, а не 21. </p>

...