Сопоставление списков с итератором float в F #
Рассмотрим следующий код:
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 # поддерживает перехват List с помощью итератора float? Для меня это источник ошибок.
Онлайн-пробная версия: http://tryfs.net/snippets/snippet-3H
Ответы
Ответ 1
Преобразование в десятичные знаки и просмотр чисел, кажется, что 21-й элемент будет "превышать" max:
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
Ответ 2
Правильная обработка проблем с поплавками может быть сложной задачей. Вы не должны полагаться на равенство float (то, что понимание списка неявно делает для последнего элемента). Перечисление списков в 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 ]
Ответ 3
Я подозреваю, что проблема связана с точностью значений с плавающей запятой. F # каждый раз добавляет dl к текущему значению и проверяет, является ли ток <= max. Из-за прецизионных задач он может перепрыгнуть через max, а затем проверить, будет ли max + ε <= max (что даст ложь). И поэтому результат будет иметь только 20 элементов, а не 21.
Ответ 4
После запуска кода, если вы выполните:
> 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 раз.
Итак, если вы используете оператор диапазона с поплавками, вы должны знать о проблеме точности.
Но то же самое относится к любому другому вычислению с поплавками.