Расчет числа е с использованием Raku - PullRequest
9 голосов
/ 17 января 2020

Я пытаюсь вычислить e константу ( AKA Эйлера ), вычислив формулу e

Для того, чтобы вычислить факториал и деление в одном кадре я написал это:

my @e = 1, { state $a=1; 1 / ($_ * $a++) } ... *;
say reduce  * + * , @e[^10];

Но это не сработало. Как это сделать правильно?

Ответы [ 2 ]

11 голосов
/ 18 января 2020

Я анализирую ваш код в разделе Анализирую ваш код . Перед этим я представляю пару забавных разделов бонусного материала.

Один вкладыш Одна буква 1

say e; # 2.718281828459045

"Трактат по нескольким направлениям " 2

Нажмите на ссылку выше, чтобы увидеть необычную статью Дамиана Конвея о вычислениях e в Раку.

В статье много весело (в конце концов, это Дамиан). Это очень понятное обсуждение вычислительной техники e. И это дань уважения к бикарбонатному перевоплощению Раку философии TIMTOWTDI, поддерживаемой Ларри Уоллом.

Учитывая, что все эти эффективные методы работают одинаково - суммируя (начальное подмножество) бесконечный ряд терминов - возможно, было бы лучше, если бы у нас была функция, которая делала бы это для нас. И, конечно, было бы лучше, если бы функция могла сама определить, сколько именно этого начального подмножества ряда она действительно должна включить, чтобы получить точный ответ ... вместо того, чтобы требовать от нас вручную прочесывать результаты множество испытаний, чтобы обнаружить это.

И, как это часто бывает в Raku, на удивление легко построить именно то, что нам нужно:

sub Σ (Unary $block --> Numeric) {
  (0..∞).map($block).produce(&[+]).&converge
}

Анализ вашего кода

Вот первая строка, генерирующая ряд:

my @e = 1, { state $a=1; 1 / ($_ * $a++) } ... *;

Замыкание ({ code goes here }) вычисляет термин. Замыкание имеет подпись, неявную или явную, которая определяет, сколько аргументов оно примет. В этом случае нет явной подписи. Использование $_ ( переменная "topi c" ) приводит к неявной подписи, для которой требуется один аргумент, связанный с $_.

Оператор последовательности (...) неоднократно вызывает замыкание слева от него, передавая предыдущий член в качестве аргумента замыкания, лениво строит серию терминов до конечной точки справа, которая в этом случае составляет *, сокращение от Inf он же бесконечность.

topi c в первом вызове замыкания равен 1. Таким образом, замыкание вычисляет и возвращает 1 / (1 * 1), что дает первые два слагаемых в серии как 1, 1/1.

Топи c в вызове second является значением предыдущего , 1/1, то есть 1 снова. Таким образом, замыкание вычисляет и возвращает 1 / (1 * 2), расширяя ряд до 1, 1/1, 1/2. Все выглядит хорошо.

Следующее закрытие вычисляет 1 / (1/2 * 3), что составляет 0.666667. Этот термин должен быть 1 / (1 * 2 * 3). Упс.

Приведение вашего кода в соответствие с формулой

Ваш код должен соответствовать формуле:
e

В этой формуле каждый член вычисляется на основе на его позиции в серии. k -й член в ряду (где k = 0 для первого 1) является просто множителем k .

(Таким образом, он не имеет ничего общего со значением предыдущего термина. Таким образом, $_, который получает значение предыдущего термина, не должно использоваться в замыкании .)

Давайте создадим постфакторный оператор факториала:

sub postfix:<!> (\k) { [×] 1 .. k }

(× - оператор умножения инфиксов, более приятный на вид псевдоним Юникода обычного инфикса ASCII *.)

Это сокращение для:

sub postfix:<!> (\k) { 1 × 2 × 3 × .... × k }

(я использовал псевдо метасинтактию c внутри скобок, чтобы обозначить идею добавления или вычитания столько терминов, сколько обязательный.

В более общем смысле, вставляя инфиксный оператор op в квадратные скобки в начале выражения, формируется составной префиксный оператор, эквивалентный reduce with => &[op],. См. Метаоператор сокращения для получения дополнительной информации.

Теперь мы можем переписать замыкание, чтобы использовать новые фактории Оператор постфикса:

my @e = 1, { state $a=1; 1 / $a++! } ... *;

Бин go. Это производит правильную серию.

... до тех пор, пока это не произойдет, по другой причине. Следующая проблема - точность чисел c. Но давайте разберемся с этим в следующем разделе.

Одна строка, полученная из вашего кода

Может быть сжать три строки до одной:

say [+] .[^10] given 1, { 1 / [×] 1 .. ++$ } ... Inf

.[^10] относится к topi c, который устанавливается given. (^10 является сокращением для 0..9, поэтому вышеприведенный код вычисляет сумму первых десяти членов в серии.)

Я исключил $a из замыкания, вычисляя следующий член. Lone $ - это то же самое, что и (state $), скаляр анонимного состояния. Я сделал предварительное увеличение вместо постинкрементного, чтобы добиться того же эффекта, что и вы, инициализируя $a в 1.

Теперь у нас осталась последняя (большая!) Проблема, Вы указали в комментарии ниже.

При условии, что ни один из его операндов не является Num (число с плавающей запятой и, следовательно, приблизительно), оператор / обычно возвращает 100% точность Rat (рациональная ограниченная точность). Но если знаменатель результата превышает 64 бита, то этот результат преобразуется в Num - что обменивает производительность на точность, компромисс, который мы не хотим делать. Мы должны принять это во внимание.

Чтобы указать неограниченная точность , а также точность 100%, просто принудительно используйте операцию FatRat с. Чтобы сделать это правильно, просто сделайте (по крайней мере) один из операндов FatRat (а остальные - Num):

say [+] .[^500] given 1, { 1.FatRat / [×] 1 .. ++$ } ... Inf

Я проверил это до 500 десятичных цифр. Я ожидаю, что он останется точным до тех пор, пока не произойдет сбой программы из-за превышения некоторого ограничения языка Raku или компилятора Rakudo. (См. мой ответ на Невозможно распаковать bigint 65536 бит шириной в собственное целое число для некоторого обсуждения этого.)

Сноски

1 В Raku встроено несколько важных математических констант, включая e, i и pi (и его псевдоним π). Таким образом, «Идентичность Эйлера» можно написать в «Раку» так, как это выглядит в математических книгах. С благодарностью за запись Raku RosettaCode для Идентификации Эйлера :

# There's an invisible character between <> and i⁢π character pairs!
sub infix:<⁢> (\left, \right) is tighter(&infix:<**>) { left * right };

# Raku doesn't have built in symbolic math so use approximate equal 
say e**i⁢π + 1 ≅ 0; # True

2 Статья Дамиана должна быть прочитана. Но это лишь одна из нескольких замечательных процедур, которые входят в число 100+ совпадений для google для 'raku "номер Эйлера" * .

3 См. TIMTOWTDI против TSBO-APOO-OWTDI для одного из более сбалансированных представлений TIMTOWTDI, написанных поклонником python. Но у есть минус слишком далеко зайти в TIMTOWTDI. Чтобы отразить эту последнюю «опасность», сообщество Perl изобрело юмористически длинный, нечитаемый и недооцененный TIMTOWTDIBSCINABTE - Есть больше, чем один способ сделать это, но иногда последовательность не так уж плоха, произносится как "Тим Бобикарбонат Toady". Как ни странно , Ларри применил бикарбонат к дизайну Раку, а Дамиан применил его для вычисления e в Раку.

9 голосов
/ 18 января 2020

Есть дроби в $_. Таким образом, вам нужно 1 / (1/$_ * $a++), а точнее $_ /$a++.

. С помощью Раку вы можете сделать этот шаг за шагом шаг за шагом

1.FatRat,1,2,3 ... *   #1 1 2 3 4 5 6 7 8 9 ...
andthen .produce: &[*] #1 1 2 6 24 120 720 5040 40320 362880
andthen .map: 1/*      #1 1 1/2 1/6 1/24 1/120 1/720 1/5040 1/40320 1/362880 ...
andthen .produce: &[+] #1 2 2.5 2.666667 2.708333 2.716667 2.718056 2.718254 2.718279 2.718282 ...
andthen .[50].say      #2.71828182845904523536028747135266249775724709369995957496696762772
...