Сопоставление списков с итератором 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 раз.

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