Я анализирую ваш код в разделе Анализ вашего кода . Перед этим я представляю пару забавных разделов бонусного материала.
Один вкладыш Одна буква 1
say e; # 2.718281828459045
Нажмите на ссылку выше, чтобы увидеть необычную статью Дамиана Конвея о компьютерах e
в Раку.
Статья очень веселая (в конце концов, это Дамиан). Это очень понятное обсуждение вычислительной техники e
. И это дань уважения бикарбонатному перевоплощению Раку философии TIMTOWTDI, поддерживаемой Ларри Уоллом. 3
В качестве закуски вот цитата из середины статьи:
Учитывая, что все эти эффективные методы работают одинаково - суммируя (начальное подмножество) бесконечный ряд терминов - возможно, было бы лучше, если бы у нас была функция, которая сделала бы это для нас. И, конечно, было бы лучше, если бы функция могла сама определить, сколько именно этого начального подмножества ряда она должна фактически включить, чтобы получить точный ответ ... вместо того, чтобы требовать от нас вручную прочесывать результаты несколько испытаний, чтобы обнаружить это.
И, как это часто бывает в Raku, на удивление легко построить именно то, что нам нужно:
sub Σ (Unary $block --> Numeric) {
(0..∞).map($block).produce(&[+]).&converge
}
Анализируя ваш код
Вот первая строка, генерирующая серию:
my @e = 1, { state $a=1; 1 / ($_ * $a++) } ... *;
Закрытие ( { code goes here }
) вычисляет термин. Замыкание имеет подпись, неявную или явную, которая определяет, сколько аргументов оно примет. В этом случае нет явной подписи. Использование $_
( переменная "topic" ) приводит к неявной подписи, которая требует одного аргумента, с которым связан $_
.
Оператор sequence ( ...
) неоднократно вызывает замыкание слева от него, передавая предыдущий член в качестве аргумента замыкания, чтобы лениво построить ряд терминов до конечной точки справа, что в данном случае является *
сокращением для Inf
бесконечности.
Тема в первом звонке на закрытие есть 1
. Таким образом, замыкание вычисляет и возвращает, 1 / (1 * 1)
получая первые два слагаемых в серии как 1, 1/1
.
Тема во втором вызове является значением предыдущего 1/1
, то есть 1
снова. Таким образом, замыкание вычисляется и возвращается 1 / (1 * 2)
, расширяя ряд до 1, 1/1, 1/2
. Все выглядит хорошо.
Следующее закрытие вычисляет, 1 / (1/2 * 3)
что есть 0.666667
. Этот термин должен быть 1 / (1 * 2 * 3)
. К сожалению.
Чтобы ваш код соответствовал формуле
Ваш код должен соответствовать формуле:
В этой формуле каждый член вычисляется на основе его положения в ряду. К - го члена ряда (где к = 0 для первого 1
) просто факториала к «с взаимным.
(Таким образом, он не имеет ничего общего со значением предыдущего термина. Таким образом, тот $_
, который получает значение предыдущего срока, не должен использоваться в закрытии.)
Давайте создадим факториальный постфиксный оператор:
sub postfix:<!> (\k) { [×] 1 .. k }
( ×
это оператор умножения инфиксов, более привлекательный псевдоним Юникода для обычного инфикса ASCII *
.)
Это сокращение для:
sub postfix:<!> (\k) { 1 × 2 × 3 × .... × k }
(Я использовал псевдо метасинтаксические обозначения внутри фигурных скобок, чтобы обозначить идею добавления или вычитания столько терминов, сколько требуется.
В более общем смысле, помещая инфиксный оператор op
в квадратные скобки в начале выражения, формируется составной префиксный оператор, эквивалентный reduce with => &[op],
. См. Сокращение метаоператора для получения дополнительной информации.
Теперь мы можем переписать замыкание для использования нового факториального постфиксного оператора:
my @e = 1, { state $a=1; 1 / $a++! } ... *;
Бинго. Это производит правильную серию.
... пока этого не произойдет, по другой причине. Следующая проблема - численная точность. Но давайте разберемся с этим в следующем разделе.
Одна строка, полученная из вашего кода
Может быть, сжать три строки до одной:
say [+] .[^10] given 1, { 1 / [×] 1 .. ++$ } ... Inf
.[^10]
относится к теме, которая установлена given
. ( ^10
это условное 0..9
обозначение, поэтому вышеприведенный код вычисляет сумму первых десяти членов в ряду.)
Я исключил $a
из вычисления замыкания следующий термин. Лоун $
такая же , как (state $)
, в anonynous состояния скаляр. Я сделал предварительное увеличение вместо постинкрементного, чтобы добиться того же эффекта, что и вы, выполнив инициализацию $a
для 1
.
Теперь у нас осталась последняя (большая!) Проблема, указанная вами в комментарии ниже.
При условии, что ни один из его операндов не является Num
(с плавающей точкой, и, следовательно, приблизительным), /
оператор обычно возвращает 100% точность Rat
(рациональная ограниченная точность). Но если знаменатель результата превышает 64 бита, то этот результат преобразуется в Num
- что обменивает производительность на точность, компромисс, который мы не хотим делать. Мы должны принять это во внимание.
Чтобы указать неограниченную точность, а также точность 100%, просто принудительно используйте операцию FatRat
s. Чтобы сделать это правильно, просто сделайте (по крайней мере) один из операндов быть FatRat
(и ни один другой не будет Num
):
say [+] .[^500] given 1, { 1.FatRat / [×] 1 .. ++$ } ... Inf
Я подтвердил это до 500 десятичных цифр. Я ожидаю, что он останется точным до тех пор, пока не произойдет сбой программы из-за превышения некоторого ограничения языка Raku или компилятора Rakudo. (См. Мой ответ на Невозможно распаковать bigint 65536 бит в родное целое для некоторого обсуждения этого.)
Сноски
1 Раку имеет несколько важных математических констант , построенных в том числе 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 - существует больше, чем один способ сделать это, но иногда последовательность не является плохой вещью, произносится «Тим Бобикарбонат Тим». Как ни странно , Ларри применил бикарбонат к дизайну Раку, а Дамиан применил его к вычислениям e
в Раку.