Pattern Matching with float (типа Double) в F # - PullRequest
3 голосов
/ 04 мая 2019

Может кто-нибудь объяснить, почему в выражениях обработки шаблонов F # не нравится float (типа Double)?

let intDiv x y =
    match x / y with
    | 5 -> printfn "Result was five - congrats"; 5
    | z -> printfn "Result was something else: %i" z; z

Соответствующий тип int: работает как задумано.

let floatDiv x y =
    match x / y with
    | nan      -> printfn "Homie, what did you even do?"; nan
    | infinity -> printfn "Wow, you got infinity!"; infinity
    | 5.0      -> printfn "Result was 5 - proud of you"; 5.0
    | z        -> printfn "Result was something else: %i" z; z

Здесь регистр совпадений nan будет сопоставляться каждый раз, и компилятор также предупреждает меня об этом - однако, по-видимому, он также вернет правильный результат.Я ожидал этого, только если это соответствует последнему случаю z.

> floatDiv 10. 3.;;
Homie, what did you even do?
val it : float = 3.333333333

1 Ответ

3 голосов
/ 04 мая 2019

Как вы заметили, nan и infinity рассматриваются здесь как идентификаторы, и значение привязано к ним.

Если проверить спецификацию F #: https://fsharp.org/specs/language-spec/4.1/FSharpSpec-4.1-latest.pdf

Глава 7 (стр. 115) говорит, что выражение const является шаблоном.

Глава 4 (стр. 36) говорит, что const включает ieee64.

Глава 3 (стр. 29) говоритчто ieee64 является либо float, либо целым числом, за которым следует LF

В месте выборки мы находим определение для float.

token float =        
   digit+ . digit*         
   digit+ (. digit* )? (e|E) (+|-)? digit+  

Это определение толькоохватывает случаи как 123, 3.14, 1E99 и так далее.Это не включает бесконечность, ни nan.

Таким образом, согласно спецификации, вышеупомянутое поведение предназначено.Это должно быть изменено?Возможно, но включает обновление языка, чтобы включить nan и бесконечность как часть константных выражений для float.Поскольку ieee включает эти значения, имеет смысл, я думаю, что оно должно быть частью константного выражения.

Однако, изменение, вероятно, рискованно, поскольку внезапно в старом коде nan означало ссылку на метод, с изменением это будет литерал с плавающей точкой.Может быть, кто-то использовал nan в качестве имени функции?Теперь это может привести к сбою, потому что это будет похоже на присвоение функции имени: 0.

Как уже упоминалось @Foole, вы можете решить эту проблему с помощью Active Patterns.

// Define the Active Pattern
let (|Float|Infinity|NaN|) n =
  if System.Double.IsPositiveInfinity   n then Infinity true
  elif System.Double.IsNegativeInfinity n then Infinity false
  elif System.Double.IsNaN              n then NaN
  else Float n

// We can then use the Active Pattern as a "smart" pattern
let floatDiv x y =
  match x / y with
  | NaN          -> printfn "Homie, what did you even do?"; nan
  | Infinity _   -> printfn "Wow, you got infinity!"; infinity
  | Float    5.0 -> printfn "Result was 5 - proud of you"; 5.0
  | Float    z   -> printfn "Result was something else: %f" z; z

let run () =
  floatDiv 1.0 2.0 |> printfn "%A"
  floatDiv 5.0 1.0 |> printfn "%A"
  floatDiv 1.0 0.0 |> printfn "%A"
  floatDiv 0.0 0.0 |> printfn "%A"

Сравнение чисел с плавающей точкойдля определенного числа всегда немного «рискованно», поскольку плавающие числа по своей природе часто являются лишь приблизительным ответом.Обычно сравнивают результат с диапазоном допусков.

Кроме того;NAN сбивает с толку многих разработчиков тем, что большинство сравнений с использованием Nan являются ложными.

1.0 < nan // false  <-|
nan < 1.0 // false, <-| these inequalities can break balanced trees algorithms if you use float as a key and happen to insert a nan

nan = nan  // false  <-|
nan <> nan // true   <-| perhaps also surprising?
...