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