pisano_periods_efficient_algorithm.pl 1.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. #!/usr/bin/perl
  2. # Daniel "Trizen" Șuteu
  3. # Date: 22 September 2018
  4. # https://github.com/trizen
  5. # Efficient algorithm for computing the Pisano period: period of Fibonacci
  6. # numbers mod `n`, assuming that the factorization of `n` can be computed.
  7. # See also:
  8. # https://oeis.org/A001175
  9. # https://oeis.org/A053031
  10. # https://en.wikipedia.org/wiki/Pisano_period
  11. # https://en.wikipedia.org/wiki/Wall%E2%80%93Sun%E2%80%93Sun_prime
  12. use 5.020;
  13. use strict;
  14. use warnings;
  15. use experimental qw(signatures);
  16. use List::Util qw(first);
  17. use ntheory qw(divisors factor_exp);
  18. use Math::AnyNum qw(:overload kronecker fibmod lcm factorial);
  19. sub pisano_period_pp ($p, $k = 1) {
  20. $p**($k - 1) * first { fibmod($_, $p) == 0 } divisors($p - kronecker($p, 5));
  21. }
  22. sub pisano_period($n) {
  23. return 0 if ($n <= 0);
  24. return 1 if ($n == 1);
  25. my $d = lcm(map { pisano_period_pp($_->[0], $_->[1]) } factor_exp($n));
  26. foreach my $k (0 .. 2) {
  27. my $t = $d << $k;
  28. if ((fibmod($t, $n) == 0) and (fibmod($t + 1, $n) == 1)) {
  29. return $t;
  30. }
  31. }
  32. die "Conjecture disproved for n=$n";
  33. }
  34. say pisano_period(factorial(10)); #=> 86400
  35. say pisano_period(factorial(30)); #=> 204996473853050880000000
  36. say pisano_period(2**128 + 1); #=> 28356863910078205764000346543980814080
  37. say join(', ', map { pisano_period($_) } 1 .. 20); #=> 1, 3, 8, 6, 20, 24, 16, 12, 24, 60, 10, 24, 28, 48, 40, 24, 36, 24, 18, 60