Проблема Фибоначчи вызывает переполнение арифмети c - PullRequest
1 голос
/ 01 марта 2020

Проблема: создать функцию с одним входом. Вернуть индекс массива, содержащего последовательность Фибоначчи (начиная с 0), элемент которой соответствует входному значению функции.

  16 ~ │ def fib(n)
  17 ~ │   return 0 if n == 0
  18   │ 
  19 ~ │   last    = 0u128
  20 ~ │   current = 1u128
  21   │ 
  22 ~ │   (n - 1).times do
  23 ~ │     last, current = current, last + current
  24   │   end
  25 + │ 
  26 + │   current
  27   │ end
  28   │
  60   │ def usage
  61   │   progname = String.new(ARGV_UNSAFE.value)
  62   │ 
  63   │   STDERR.puts <<-H
  64   │   #{progname} <integer>
  65   │     Given Fibonacci; determine which fib value would
  66   │     exist at <integer> index.
  67   │   H
  68   │ 
  69   │   exit 1
  70   │ end
  71   │ 
  72   │ if ARGV.empty?
  73   │   usage
  74   │ end
  75   │ 
  76   │ begin
  77 ~ │   i = ARGV[0].to_i
  78 ~ │   puts fib i
  79   │ rescue e
  80   │   STDERR.puts e
  81   │   usage
  82   │ end

Мое решение проблемы ни в коем случае не изящно, и я сделал это в 2 часа ночи, когда Я был довольно уставшим. Поэтому я не ищу более элегантное решение. Что меня интересует, так это то, что если я запускаю результирующее приложение с входным значением больше 45, то я получаю Arithmetic overflow. Я думаю, что сделал что-то не так с набором переменных Я запустил это в Ruby, и он работает нормально, поэтому я знаю, что это не проблема с оборудованием ...

Может ли кто-нибудь помочь мне найти, что я сделал не так в этом? Я тоже все еще копаю. Я только начал работать с Кристаллом на этой неделе. Это мое второе приложение / эксперимент с ним. Мне действительно нравится, но я еще не знаю о некоторых его особенностях.

РЕДАКТИРОВАТЬ

Обновленный скрипт, чтобы отразить предлагаемое изменение и результат времени выполнения от указанного изменения. С этим изменением я могу теперь успешно запустить программу с номером 45, но только до 90-х. Это интересно. Я собираюсь пройти через это и посмотреть, где мне может понадобиться добавить дополнительное явное приведение. Кажется, очень не интуитивно понятно, что изменение типа во время инициации не «зависало» на протяжении всего времени выполнения, которое я попробовал первым, и это не удалось. Что-то здесь не имеет смысла для меня.

Исходные результаты

$ crystal build fib.cr
$ ./fib 45
1836311903
$ ./fib 46
Arithmetic overflow
$ ./fib.rb 460
985864329041134079854737521712801814394706432953315\
510410398508752777354792040897021902752675861

Последние результаты

$ ./fib 92
12200160415121876738
$ ./fib 93
Arithmetic overflow
./fib <integer>
  Given Fibonacci; determine which fib value would
  exist at <integer> index.

Edit ^ 2

Теперь также решили, что, возможно, ARGV[0] является проблемой. Поэтому я изменил вызов на f() на:

 62 begin                                                                                                                                                                           
 63   i = ARGV[0].to_u64.as(UInt64)                                                                                                                                                 
 64   puts f i                                                                                                                                                                      
 65 rescue e                                                                                                                                                                        
 66   STDERR.puts e                                                                                                                                                                 
 67   usage                                                                                                                                                                         
 68 end

и добавил отладочную печать, чтобы показать типы используемых переменных:

22   return 0 if p == 0                                                                                                                                                            
 23                                                                                                                                                                                 
 24   puts "p: %s\tfib_now: %s\tfib_last: %s\tfib_hold: %s\ti: %s" % [typeof(p), typeof(fib_now), typeof(fib_last), typeof(fib_hold), typeof(i)]                                    
 25   loop do
p: UInt64       fib_now: UInt64 fib_last: UInt64        fib_hold: UInt64        i: UInt64
Arithmetic overflow
./fib <integer>
  Given Fibonacci; determine which fib value would
  exist at <integer> index.

Редактировать ^ 3

Обновлен с последним кодом после исправления ошибки Jonne. Оказывается, проблема в том, что я пересекаю границы структуры даже с 128-битными целыми числами без знака. Ruby обрабатывает это изящно. Похоже, что в хрустале я должен изящно справиться с этим.

1 Ответ

4 голосов
/ 01 марта 2020

Целочисленный тип по умолчанию в Crystal - Int32, поэтому, если вы не укажете явно тип целочисленного литерала, вы получите его.

В частности, строки

fib_last = 0
fib_now  = 1

превратить переменные в эффективный тип Int32. Чтобы исправить это, убедитесь, что вы указали тип этих целых чисел, поскольку вам не нужны отрицательные числа, UInt64 кажется наиболее подходящим здесь:

fib_last = 0u64
fib_now  = 1u64

Также обратите внимание на буквальный синтаксис, который я использую Вот. Ваши 0.to_i64 создают In32, а затем Int64 из этого. Компилятор будет достаточно умен, чтобы выполнить это преобразование во время компиляции в сборках релиза, но я думаю, что лучше использовать буквальный синтаксис.

Редактировать, отвечая на обновленный вопрос

Фибоначчи определяется как F 0 = 0, F 1 = 1, F n = F n-2 + F n-1 , поэтому 0, 1, 1, 2, 3, 5. Ваш алгоритм отключен на единицу. Он вычисляет F n + 1 для данного n> 1, другими словами 0, 1, 2, 3, 5, иными словами, в основном он пропускает F 2 .

Вот тот, который делает это правильно:

def fib(n)
  return 0 if n == 0

  last = 0u64
  current = 1u64

  (n - 1).times do
    last, current = current, last + current
  end

  current
end

Это правильно дает 7540113804746346429 для F 92 и 12200160415121876738 для F 93 . Однако он все равно переполняется для F 94 , потому что это будет 19740274219868223167, который больше, чем 2 64 = 18446744073709551616, поэтому он не вписывается в UInt64. Чтобы уточнить еще раз, ваша версия пытается вычислить F 94 при запросе F 93 , следовательно, вы получите его "слишком рано".

Так что если вы хотите чтобы поддержать расчет F n для n> 93, вам нужно рискнуть получить экспериментальную поддержку Int128 / UInt128 или использовать BigInt.

...