modular_lucas_numbers.pl 1.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. #!/usr/bin/perl
  2. # Efficient algorithm for computing the nth-Lucas number (mod m).
  3. # Algorithm from:
  4. # https://metacpan.org/source/KRYDE/Math-NumSeq-72/lib/Math/NumSeq/LucasNumbers.pm
  5. # See also:
  6. # https://en.wikipedia.org/wiki/Lucas_number
  7. use 5.020;
  8. use warnings;
  9. use experimental qw(signatures);
  10. use Math::GMPz;
  11. use Math::Prime::Util::GMP qw(gcd consecutive_integer_lcm);
  12. sub lucasmod ($n, $m) {
  13. $n = Math::GMPz->new("$n");
  14. $m = Math::GMPz->new("$m");
  15. my ($f, $g, $w) = (
  16. Math::GMPz::Rmpz_init_set_ui(3),
  17. Math::GMPz::Rmpz_init_set_ui(1),
  18. );
  19. foreach my $bit (split(//, substr(Math::GMPz::Rmpz_get_str($n, 2), 1))) {
  20. Math::GMPz::Rmpz_powm_ui($g, $g, 2, $m);
  21. Math::GMPz::Rmpz_powm_ui($f, $f, 2, $m);
  22. $w
  23. ? do {
  24. Math::GMPz::Rmpz_sub_ui($g, $g, 2);
  25. Math::GMPz::Rmpz_add_ui($f, $f, 2);
  26. }
  27. : do {
  28. Math::GMPz::Rmpz_add_ui($g, $g, 2);
  29. Math::GMPz::Rmpz_sub_ui($f, $f, 2);
  30. };
  31. if ($bit) {
  32. Math::GMPz::Rmpz_sub($g, $f, $g);
  33. $w = 0;
  34. }
  35. else {
  36. Math::GMPz::Rmpz_sub($f, $f, $g);
  37. $w = 1;
  38. }
  39. }
  40. Math::GMPz::Rmpz_mod($g, $g, $m);
  41. return $g;
  42. }
  43. sub lucas_factorization ($n, $B = 10000) {
  44. my $k = consecutive_integer_lcm($B); # lcm(1..B)
  45. my $L = lucasmod($k, $n); # Lucas(k) (mod n)
  46. return gcd($L - 2, $n);
  47. }
  48. say lucas_factorization("121095274043", 700); #=> 470783 (p+1 is 700-smooth)
  49. say lucas_factorization("544812320889004864776853", 3000); #=> 333732865481 (p-1 is 3000-smooth)