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

Я бы рекомендовал сделать его хвостовым рекурсивным, как предложил Томас.