F # Проблемы с мощностью, которые принимают оба аргумента в виде bigints
В настоящее время я экспериментирую с F #. Статьи, найденные в Интернете, полезны, но, как программист на С#, я иногда сталкиваюсь с ситуациями, когда я думал, что мое решение поможет, но это не помогло или частично помогло.
Таким образом, моя нехватка знаний о F # (и, скорее всего, как работает компилятор), вероятно, является причиной того, что иногда я часто ошеломлен.
Например, я написал программу С# для определения совершенных чисел. Он использует известную форму доказательства Евклидов, что идеальное число может быть образовано из Первого Мерсенна 2p-1 (2p-1) (где 2p-1 является простым, а p обозначается как степень).
Поскольку с помощью F # указано, что '**' может использоваться для вычисления мощности, но использует плавающие точки, я попытался создать простую функцию с оператором бит-сдвига (< < <) (обратите внимание, что я 'отредактируйте этот код для указания необходимости):
let PowBitShift (y:int32) = 1 <<< y;;
Однако при запуске теста и поиске улучшений производительности я также попытался использовать форму, которую я помню, используя Миранду (язык функционального программирования), который использует рекурсию и шаблонный шаблон для вычисления мощности. Основное преимущество заключается в том, что я могу использовать переменную y как 64-битное целое число, что невозможно при использовании стандартного оператора бит-брейка.
let rec Pow (x : int64) (y : int64) =
match y with
| 0L -> 1L
| y -> x * Pow x (y - 1L);;
Оказывается, эта функция работает быстрее, но я не могу (пока) понять причину. Возможно, это менее интеллектуальный вопрос, но мне все еще интересно.
Тогда вопрос секунд будет заключаться в том, что при вычислении совершенных чисел вы сталкиваетесь с тем фактом, что int64 не может отображать большие числа, пересекающие их после нахождения 9-го совершенного числа (которое формируется из мощности 31). Я пытаюсь выяснить, можете ли вы использовать объект BigInteger (или тип bigint), но здесь мое знание F # меня немного блокирует. Возможно ли создать силовую функцию, которая принимает оба аргумента как bigints?
В настоящее время у меня есть это:
let rec PowBigInt (x : bigint) (y : bigint) =
match y with
| bigint.Zero -> 1I
| y -> x * Pow x (y - 1I);;
Но это порождает ошибку, которая bigint.Zero не определена. Поэтому я тоже делаю что-то неправильно. 0I не принимается за замену, так как он дает эту ошибку:
Non-primitive numeric literal constants cannot be used in pattern matches because they
can be mapped to multiple different types through the use of a NumericLiteral module.
Consider using replacing with a variable, and use 'when <variable> = <constant>' at the
end of the match clause.
Но шаблонный шаблон не может использовать оператор "когда". Есть ли другое решение для этого?
Спасибо заранее, и прошу простить мой длинный пост. Я только пытаюсь выразить свои "вызовы" как можно яснее.
Ответы
Ответ 1
Я не понял, зачем вам y
быть int64
или bigint
. Согласно этой ссылке, самое большое известное число Мерсенны - это номер с p = 43112609
, где p
действительно находится внутри диапазона int
.
Имея y
как целое число, вы можете использовать стандартный оператор pown : ^T -> int -> ^T
вместо этого, потому что:
let Pow (x : int64) y = pown x y
let PowBigInt (x: bigint) y = pown x y
Что касается вашего вопроса о сопоставлении с образцом bigint
, сообщение об ошибке достаточно ясно показывает, что вы можете использовать сопоставление шаблонов с помощью when
охранников:
let rec PowBigInt x y =
match y with
| _ when y = 0I -> 1I
| _ -> x * PowBigInt x (y - 1I)
Ответ 2
Я думаю, что самый простой способ определить PowBigInt
- использовать if
вместо соответствия шаблону:
let rec PowBigInt (x : bigint) (y : bigint) =
if y = 0I then 1I
else x * PowBigInt x (y - 1I)
Проблема заключается в том, что bigint.Zero
является статическим свойством, которое возвращает значение, но шаблоны могут содержать только (константные) литералы или активные шаблоны F #. Они не могут напрямую содержать вызовы свойств (или других). Однако вы можете написать дополнительные ограничения в where
, если вы все еще предпочитаете match
:
let rec PowBigInt (x : bigint) (y : bigint) =
match y with
| y when y = bigint.Zero -> 1I
| y -> x * PowBigInt x (y - 1I)
В качестве побочной заметки вы, вероятно, можете сделать функцию более эффективной с помощью хвостовой рекурсии (идея состоит в том, что если функция делает рекурсивный вызов последней, то ее можно скомпилировать более эффективно):
let PowBigInt (x : bigint) (y : bigint) =
// Recursive helper function that stores the result calculated so far
// in 'acc' and recursively loops until 'y = 0I'
let rec PowBigIntHelper (y : bigint) (acc : bigint) =
if y = 0I then acc
else PowBigIntHelper (y - 1I) (x * acc)
// Start with the given value of 'y' and '1I' as the result so far
PowBigIntHelper y 1I
Что касается функции PowBitShift
- я не уверен, почему она медленнее, но она определенно не делает то, что вам нужно. Использование битового сдвига для реализации мощности работает только тогда, когда база равна 2.
Ответ 3
Вам не нужно создавать функцию Pow.
Оператор (**) имеет перегрузку для bigint → int → bigint.
Только второй параметр должен быть целым числом, но я не думаю, что проблема для вашего дела.
Просто попробуйте
bigint 10 ** 32;;
val it : System.Numerics.BigInteger =
100000000000000000000000000000000 {IsEven = true;
IsOne = false;
IsPowerOfTwo = false;
IsZero = false;
Sign = 1;}
Ответ 4
Другим вариантом является встроенная функция, поэтому она работает со всеми числовыми типами (поддерживающими требуемые операторы: (*)
, (-)
, get_One
и get_Zero
).
let rec inline PowBigInt (x:^a) (y:^a) : ^a =
let zero = LanguagePrimitives.GenericZero
let one = LanguagePrimitives.GenericOne
if y = zero then one
else x * PowBigInt x (y - one)
let x = PowBigInt 10 32 //int
let y = PowBigInt 10I 32I //bigint
let z = PowBigInt 10.0 32.0 //float
Я бы рекомендовал сделать его хвостовым рекурсивным, как предложил Томас.