partial_sums_of_gcd-sum_function.pl 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. #!/usr/bin/perl
  2. # Author: Daniel "Trizen" Șuteu
  3. # Date: 20 November 2018
  4. # https://github.com/trizen
  5. # A new algorithm for computing the partial-sums of the gcd-sum function `Sum_{d|k} d*ϕ(k/d)`, for `1 <= k <= n`:
  6. #
  7. # a(n) = Sum_{k=1..n} Sum_{d|k} d*phi(k/d)
  8. #
  9. # where phi(k) is the Euler totient function.
  10. # Also equivalent with:
  11. # a(n) = Sum_{j=1..n} Sum_{i=1..j} gcd(i, j)
  12. # Based on the formula:
  13. # a(n) = (1/2)*Sum_{k=1..n} phi(k) * floor(n/k) * floor(1+n/k)
  14. # Example:
  15. # a(10^1) = 122
  16. # a(10^2) = 18065
  17. # a(10^3) = 2475190
  18. # a(10^4) = 317257140
  19. # a(10^5) = 38717197452
  20. # a(10^6) = 4571629173912
  21. # a(10^7) = 527148712519016
  22. # a(10^8) = 59713873168012716
  23. # a(10^9) = 6671288261316915052
  24. # This algorithm can be vastly improved.
  25. # See also:
  26. # https://oeis.org/A018804
  27. # https://oeis.org/A272718
  28. # https://en.wikipedia.org/wiki/Mertens_function
  29. # https://en.wikipedia.org/wiki/M%C3%B6bius_function
  30. # https://en.wikipedia.org/wiki/Euler%27s_totient_function
  31. # https://trizenx.blogspot.com/2018/11/partial-sums-of-arithmetical-functions.html
  32. use 5.020;
  33. use strict;
  34. use warnings;
  35. use Math::GMPz qw();
  36. use experimental qw(signatures);
  37. use ntheory qw(euler_phi moebius mertens sqrtint forsquarefree);
  38. sub euler_totient_partial_sum ($n) {
  39. my $total = Math::GMPz->new(0);
  40. my $s = sqrtint($n);
  41. my $u = int($n / ($s + 1));
  42. my $prev = mertens($n);
  43. for my $k (1 .. $s) {
  44. my $curr = mertens(int($n / ($k + 1)));
  45. $total += ($prev - $curr) * $k * ($k + 1);
  46. $prev = $curr;
  47. }
  48. forsquarefree {
  49. my $t = int($n / $_);
  50. $total += moebius($_) * $t * ($t + 1);
  51. } $u;
  52. return $total / 2;
  53. }
  54. sub gcd_sum_partial_sum($n) {
  55. my $total = Math::GMPz->new(0);
  56. my $s = sqrtint($n);
  57. my $u = int($n / ($s + 1));
  58. my $prev = euler_totient_partial_sum($n);
  59. for my $k (1 .. $s) {
  60. my $curr = euler_totient_partial_sum(int($n / ($k + 1)));
  61. $total += ($prev - $curr) * $k * ($k + 1);
  62. $prev = $curr;
  63. }
  64. for my $k (1 .. $u) {
  65. my $t = int($n / $k);
  66. $total += euler_phi($k) * $t * ($t + 1);
  67. }
  68. return $total / 2;
  69. }
  70. sub gcd_sum_partial_sum_test ($n) { # just for testing
  71. my $sum = Math::GMPz->new(0);
  72. foreach my $k (1 .. $n) {
  73. my $t = int($n / $k);
  74. $sum += euler_phi($k) * $t * ($t + 1);
  75. }
  76. return $sum / 2;
  77. }
  78. for my $m (0 .. 10) {
  79. my $n = int rand 10000;
  80. my $t1 = gcd_sum_partial_sum($n);
  81. my $t2 = gcd_sum_partial_sum_test($n);
  82. die "error: $t1 != $t2" if ($t1 != $t2);
  83. say "Sum_{k=1..$n} G(k) = $t1";
  84. }
  85. __END__
  86. Sum_{k=1..6249} G(k) = 118276019
  87. Sum_{k=1..6470} G(k) = 127257585
  88. Sum_{k=1..1271} G(k) = 4109678
  89. Sum_{k=1..4849} G(k) = 69427261
  90. Sum_{k=1..6771} G(k) = 140029473
  91. Sum_{k=1..5078} G(k) = 76492429
  92. Sum_{k=1..1262} G(k) = 4054055
  93. Sum_{k=1..7751} G(k) = 185959182
  94. Sum_{k=1..4188} G(k) = 51033167
  95. Sum_{k=1..5283} G(k) = 83132565
  96. Sum_{k=1..2574} G(k) = 18289119