Вычисление константы Эйлера с помощью OCaml

#scheme #ocaml #divide-by-zero #eulers-number

#схема #ocaml #деление на ноль #эйлеры-число

Вопрос:

Я начал изучать OCaml сегодня. Я уже знал схему, поэтому подумал, что было бы неплохо попытаться перевести некоторые примеры схемных программ на ML. Ниже у меня есть некоторый код для вычисления числа Эйлера, который работает, но он не работает в OCaml. Я получаю эту ошибку: Exception: Division_by_zero. Я думаю, что это может быть проблемой при смешивании чисел с плавающей точкой и целых чисел где-нибудь. Я не смог понять, как установить точки останова для функций с. ocamldebug Кто-нибудь может определить, где происходит моя ошибка? Спасибо.

 (define (factorial n)
    (if (zero? n) 1 (* n (factorial (sub1 n)))))

(define (find-e accuracy sum)
    (if (zero? accuracy) (add1 sum)
        (find-e (sub1 accuracy) (  sum (/ 1 (factorial accuracy))))))

(display (format "~f" (find-e 100 0)))
 
 let rec factorial n = if n == 0 then 1 else n * factorial (n - 1) ;;

let rec find_e accuracy sum =
    if accuracy == 0
        then (sum   1)
        else find_e (accuracy - 1) (sum   (1 / factorial accuracy)) ;;

let result = find_e 100 0 ;;
 

Ответ №1:

Насколько я помню, в scheme есть «числовая башня», которая пытается уберечь вас от потери точности в числовых вычислениях.

В OCaml нет какой-либо причудливой автоматической обработки чисел. Ваш код использует type int , который представляет собой целое число фиксированного размера (31 или 63 бита в обычных реализациях). Таким образом, ваше выражение 1 / factorial accuracy будет равно 0 почти во всех случаях, а значения для factorial accuracy будут непредставимы для всех, кроме наименьших значений. Значение factorial 100 будет 0 , потому что оно кратно 2 ^ 63:

 # let rec fact n = if n < 2 then 1 else n * fact (n - 1);;
val fact : int -> int = <fun>
# fact 100;;
- : int = 0
 

В вашем коде нет плавающих значений, и, следовательно, не может быть никакого смешивания. Но OCaml не позволяет смешивать в первую очередь. Это строго типизированный язык, где int и float — два разных типа.

Вот ваш код, преобразованный для использования чисел с плавающей запятой:

 let rec factorial n =
    if n = 0.0 then 1.0 else n *. factorial (n -. 1.0) 

let rec find_e accuracy sum =
    if accuracy = 0.0 then
        sum  . 1.0
    else
        find_e (accuracy -. 1.0) (sum  . 1.0 /. factorial accuracy)

let result = find_e 100.0 0.0
 

Если я скопирую / вставлю это в OCaml REPL (также известный как «верхний уровень») Я вижу это:

 val factorial : float -> float = <fun>
val find_e : float -> float -> float = <fun>
val result : float = 2.71828182845904509
 

В качестве дополнительного комментария оператор сравнения равенства в OCaml является = . Не используйте == для сравнений. Это совершенно другой оператор:

 # 1.0 == 1.0;;
- : bool = false
 

Комментарии:

1. вы имеете в виду, что я должен менять каждый экземпляр int to float ?

2. Это будет работать лучше, но факториал 100 также не может быть представлен в виде числа с плавающей точкой. Вам также нужно будет изменить свои операторы, чтобы они работали с float. Я не думаю, что это домашнее задание 🙂 поэтому я напишу версию, которая использует float .

3. Означает ли это, что существуют альтернативные операторы только для чисел с плавающей точкой?

4. Да, OCaml строго типизирован. Данная функция (например ) работает только со значениями одного типа ( int в данном случае). Оператор для добавления чисел с плавающей . запятой (с завершающим периодом или десятичной точкой).

5. (Я не могу отредактировать свой комментарий выше, но факториал 100 на самом деле может быть довольно хорошо представлен в виде числа с плавающей точкой. Значения с плавающей запятой OCaml (которые на самом деле имеют двойную точность) могут представлять более широкий диапазон, чем я думал.)

Ответ №2:

Этот ответ подразумевается как дополнение к принятому в настоящее время.

Поскольку факториалы представляют собой целочисленные значения, несколько неудачно использовать числа с плавающей запятой для представления вычислений, поскольку это приводит к потере точности при достаточно больших значениях. Scheme и Lisp имеют прозрачную поддержку значений и коэффициентов, но в OCaml для этого есть библиотека.

 opam install zarith
 

Затем в вашем верхнем уровне ocaml:

 # #use "topfind";;
# #require "zarith";;
/home/user/.opam/4.11.0/lib/zarith: added to search path
/home/user/.opam/4.11.0/lib/zarith/zarith.cma: loaded
 

Существует два основных модуля, Z и Q (целые и рациональные числа), а также модуль совместимости для более старого модуля больших целых чисел. Вы, вероятно, захотите взглянуть на документацию: https://antoinemine.github.io/Zarith/doc/latest/Z.html

Библиотека определяет свой собственный тип для целых чисел:

 # Z.of_int(3);;
- : Z.t = <abstr>
 

Если вы установите pretty-printer:

 # #install_printer Z.pp_print;;
# Z.of_int(3);;
- : Z.t = 3
 

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