Справочная информация:
Меня "затащили" на этот вопрос:
Закрытое выражение Фибоначчи в Haskell
когда автор первоначально помещал на многие другие языки, но позже сосредоточился на вопросе Хаскелла. К сожалению, у меня нет опыта с Haskell, поэтому я не мог участвовать в этом вопросе. Однако один из ответов привлек мое внимание, когда ответчик превратил его в чистую целую математическую задачу. Это звучало awesome для меня, поэтому мне пришлось выяснить, как это работает, и сравнить это с рекурсивной реализацией Fibonacci, чтобы увидеть, насколько он точным был. У меня такое чувство, что если бы я просто вспомнил о соответствующей математике, включающей иррациональные числа, я мог бы самостоятельно все проработать (но не знаю). Итак, первым шагом для меня был перенос его на язык, с которым я знаком. В этом случае я делаю С#.
Я, к счастью, не совсем в темноте. У меня много опыта на другом функциональном языке (OCaml), поэтому многие из них выглядели несколько знакомыми мне. Начиная с преобразования, все казалось простым, поскольку в основном он определял новый числовой тип, который помогал бы в расчетах. Однако я ударил пару блокпостов в переводе, и мне не удается закончить его. Я получаю совершенно неправильные результаты.
Анализ:
Вот код, который я переводил:
data Ext = Ext !Integer !Integer
deriving (Eq, Show)
instance Num Ext where
fromInteger a = Ext a 0
negate (Ext a b) = Ext (-a) (-b)
(Ext a b) + (Ext c d) = Ext (a+c) (b+d)
(Ext a b) * (Ext c d) = Ext (a*c + 5*b*d) (a*d + b*c) -- easy to work out on paper
-- remaining instance methods are not needed
fib n = divide $ twoPhi^n - (2-twoPhi)^n
where twoPhi = Ext 1 1
divide (Ext 0 b) = b `div` 2^n -- effectively divides by 2^n * sqrt 5
Итак, основываясь на моих исследованиях и на том, что я могу вывести (исправьте меня, если я где-то не прав), первая часть объявит тип Ext
конструктором, который будет иметь два параметра Integer
(и, я думаю, наследует Eq
и Show
типы/модули).
Далее следует реализация Ext
, которая "выводится" из Num
. fromInteger
выполняет преобразование из Integer
. negate
- это унарное отрицание, а затем есть бинарные операторы сложения и умножения.
Последняя часть - это фактическая реализация Фибоначчи.
Вопросы:
В ответе хаммар (ответчик) упоминает, что возведение в степень осуществляется по умолчанию в Num
. Но что это значит и как это применимо к этому типу? Есть ли неявное число "поле", которое мне не хватает? Применяет ли он только возведение в степень каждого соответствующего номера? Я предполагаю, что он делает последнее и заканчивается этим кодом С#:
public static Ext operator ^(Ext x, int p) // "exponent"
{
// just apply across both parts of Ext?
return new Ext(BigInt.Pow(x.a, p), BigInt.Pow(x.b, p));
// Ext (a^p) (b^p)
}
Однако это конфликтует с тем, как я понимаю, почему negate
необходим, он не понадобится, если это действительно произойдет.
Теперь мясо кода. Я прочитал первую часть
divide $ twoPhi^n - (2-twoPhi)^n
как:
разделите результат следующего выражения: twoPhi ^ n - (2-twoPhi) ^ n.
Довольно просто. Поднимите twoPhi
в n
th. Вычтите из этого результат остального. Здесь мы делаем двоичное вычитание, но мы только реализовали унарное отрицание. Или нет? Или может быть подразумевается двоичное вычитание, потому что оно может быть составлено, комбинируя добавление и отрицание (что у нас есть)? Я предполагаю, что последний. И это облегчает мою неопределенность в отношении отрицания.
Последняя часть - это фактическое деление:
divide (Ext 0 b) = b `div` 2^n
. Здесь есть две проблемы. Из того, что я нашел, нет оператора деления, только функция `div`
. Поэтому мне просто нужно разделить числа здесь. Это верно? Или существует оператор деления, но отдельная функция `div`
, которая делает что-то еще особенное?
Я не уверен, как интерпретировать начало строки. Это просто простая совпадение? Другими словами, применимо ли это только в том случае, если первым параметром был 0
? Каким будет результат, если он не соответствует (первый был отличным от нуля)? Или мне следует интерпретировать его, поскольку мы не заботимся о первом параметре и безоговорочно применяем функцию? Это, по-видимому, самое большое препятствие, и использование любой интерпретации по-прежнему дает неверные результаты.
Я сделал какие-то неправильные предположения где-нибудь? Или все в порядке, и я просто внедрил С# неправильно?
Код:
Здесь (нерабочий) перевод и полный источник (включая тесты) пока что только в если кто-то заинтересован.
// code removed to keep post size down
// full source still available through link above
Прогресс:
Хорошо, так глядя на ответы и комментарии, я думаю, я знаю, куда идти отсюда и почему.
Экспоненциация просто необходима для выполнения того, что она обычно выполняет, умножая p
раз, учитывая, что мы реализовали операцию умножения. Мне никогда не приходило в голову, что мы должны делать то, что всегда говорил нам математический класс. Неявное вычитание из добавления и отрицания также является довольно удобной функцией.
Также заметили опечатку в моей реализации. Я добавил, когда мне нужно было умножить.
// (Ext a b) * (Ext c d) = Ext (a*c + 5*b*d) (a*d + b*c)
public static Ext operator *(Ext x, Ext y)
{
return new Ext(x.a * y.a + 5*x.b*y.b, x.a*y.b + x.b*y.a);
// ^ oops!
}
Вывод:
Итак, теперь оно завершено. Я только применил к основным операторам и немного переименовал его. Названо так же, как и комплексные числа. До сих пор, в соответствии с рекурсивной реализацией, даже при действительно больших затратах. Вот окончательный код.
static readonly Complicated TWO_PHI = new Complicated(1, 1);
static BigInt Fib_x(int n)
{
var x = Complicated.Pow(TWO_PHI, n) - Complicated.Pow(2 - TWO_PHI, n);
System.Diagnostics.Debug.Assert(x.Real == 0);
return x.Bogus / BigInt.Pow(2, n);
}
struct Complicated
{
private BigInt real;
private BigInt bogus;
public Complicated(BigInt real, BigInt bogus)
{
this.real = real;
this.bogus = bogus;
}
public BigInt Real { get { return real; } }
public BigInt Bogus { get { return bogus; } }
public static Complicated Pow(Complicated value, int exponent)
{
if (exponent < 0)
throw new ArgumentException(
"only non-negative exponents supported",
"exponent");
Complicated result = 1;
Complicated factor = value;
for (int mask = exponent; mask != 0; mask >>= 1)
{
if ((mask & 0x1) != 0)
result *= factor;
factor *= factor;
}
return result;
}
public static implicit operator Complicated(int real)
{
return new Complicated(real, 0);
}
public static Complicated operator -(Complicated l, Complicated r)
{
var real = l.real - r.real;
var bogus = l.bogus - r.bogus;
return new Complicated(real, bogus);
}
public static Complicated operator *(Complicated l, Complicated r)
{
var real = l.real * r.real + 5 * l.bogus * r.bogus;
var bogus = l.real * r.bogus + l.bogus * r.real;
return new Complicated(real, bogus);
}
}
И здесь полностью обновленный источник.