smallest_carmichael_divisible_by_n.pl 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. #!/usr/bin/perl
  2. # Method for finding the smallest Carmichael number divisible by n.
  3. # See also:
  4. # https://oeis.org/A135721
  5. # https://oeis.org/A253595
  6. use 5.036;
  7. use warnings;
  8. use ntheory qw(:all);
  9. sub divceil ($x, $y) { # ceil(x/y)
  10. (($x % $y == 0) ? 0 : 1) + divint($x, $y);
  11. }
  12. sub carmichael_from_multiple ($A, $B, $m, $L, $lo, $k, $callback) {
  13. # Largest possisble prime factor for Carmichael numbers <= B
  14. my $max_p = (1 + sqrtint(8 * $B + 1)) >> 2;
  15. my $hi = vecmin($max_p, rootint(divint($B, $m), $k));
  16. if ($lo > $hi) {
  17. return;
  18. }
  19. if ($k == 1) {
  20. $lo = vecmax($lo, divceil($A, $m));
  21. $lo > $hi && return;
  22. my $t = invmod($m, $L) // return;
  23. $t > $hi && return;
  24. $t += $L * divceil($lo - $t, $L) if ($t < $lo);
  25. for (my $p = $t ; $p <= $hi ; $p += $L) {
  26. if ($m % $p != 0 and is_prime($p)) {
  27. my $n = $m * $p;
  28. if (($n - 1) % ($p - 1) == 0) {
  29. $callback->($n);
  30. }
  31. }
  32. }
  33. return;
  34. }
  35. foreach my $p (@{primes($lo, $hi)}) {
  36. $m % $p == 0 and next;
  37. gcd($m, $p - 1) == 1 or next;
  38. __SUB__->($A, $B, $m * $p, lcm($L, $p - 1), $p + 1, $k - 1, $callback);
  39. }
  40. }
  41. sub carmichael_divisible_by ($m) {
  42. $m >= 1 or return;
  43. $m % 2 == 0 and return;
  44. is_square_free($m) || return;
  45. gcd($m, euler_phi($m)) == 1 or return;
  46. my $A = vecmax(561, $m);
  47. my $B = 2 * $A;
  48. my $L = vecmax(1, lcm(map { $_ - 1 } factor($m)));
  49. my @found;
  50. for (; ;) {
  51. for my $k ((is_prime($m) ? 2 : 1) .. 1000) {
  52. my @P;
  53. for (my $p = 3 ; scalar(@P) < $k ; $p = next_prime($p)) {
  54. if ($m % $p != 0 and $L % $p != 0) {
  55. push @P, $p;
  56. }
  57. }
  58. last if (vecprod(@P, $m) > $B);
  59. my $callback = sub ($n) {
  60. push @found, $n;
  61. $B = vecmin($B, $n);
  62. };
  63. carmichael_from_multiple($A, $B, $m, $L, $P[0], $k, $callback);
  64. }
  65. last if @found;
  66. $A = $B + 1;
  67. $B = 2 * $A;
  68. }
  69. vecmin(@found);
  70. }
  71. carmichael_divisible_by(3) == 561 or die;
  72. carmichael_divisible_by(3 * 5) == 62745 or die;
  73. carmichael_divisible_by(7 * 19) == 1729 or die;
  74. carmichael_divisible_by(47 * 89) == 62745 or die;
  75. carmichael_divisible_by(5 * 47 * 89) == 62745 or die;
  76. carmichael_divisible_by(3 * 47 * 89) == 62745 or die;
  77. carmichael_divisible_by(3 * 89) == 62745 or die;
  78. say join(', ', map { carmichael_divisible_by($_) } @{primes(3, 50)});
  79. say join(', ', map { carmichael_divisible_by($_) } 1 .. 60);
  80. __END__
  81. 561, 1105, 1729, 561, 1105, 561, 1729, 6601, 2465, 2821, 29341, 6601, 334153, 62745
  82. 561, 561, 1105, 1729, 561, 1105, 62745, 561, 1729, 6601, 2465, 2821, 561, 825265, 29341, 6601, 334153, 62745, 561, 2433601, 74165065