#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
tofloat
?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 таким образом.