sum_of_two_squares_all_solutions.pl 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. #!/usr/bin/perl
  2. # Daniel "Trizen" Șuteu
  3. # Date: 25 October 2017
  4. # https://github.com/trizen
  5. # A recursive algorithm for finding all the non-negative integer solutions to the equation:
  6. # a^2 + b^2 = n
  7. # for any given positive integer `n` for which such a solution exists.
  8. # Example:
  9. # 99025 = 41^2 + 312^2 = 48^2 + 311^2 = 95^2 + 300^2 = 104^2 + 297^2 = 183^2 + 256^2 = 220^2 + 225^2
  10. # This algorithm is efficient when the factorization of `n` can be computed.
  11. # Blog post:
  12. # https://trizenx.blogspot.com/2017/10/representing-integers-as-sum-of-two.html
  13. # See also:
  14. # https://oeis.org/A001481
  15. use 5.020;
  16. use strict;
  17. use warnings;
  18. use experimental qw(signatures);
  19. use Math::GMPz;
  20. use ntheory qw(sqrtmod factor_exp chinese forsetproduct);
  21. sub sum_of_two_squares_solutions ($n) {
  22. $n < 0 and return;
  23. $n == 0 and return [0, 0];
  24. my %sqrtmod_cache;
  25. my $find_solutions = sub ($factor_exp) {
  26. my $prod1 = 1;
  27. my $prod2 = 1;
  28. my @prod1_factor_exp;
  29. foreach my $f (@$factor_exp) {
  30. my ($p, $e) = @$f;
  31. if ($p % 4 == 3) { # p = 3 (mod 4)
  32. $e % 2 == 0 or return; # power must be even
  33. $prod2 *= Math::GMPz->new($p)**($e >> 1);
  34. }
  35. elsif ($p == 2) { # p = 2
  36. if ($e % 2 == 0) { # power is even
  37. $prod2 *= Math::GMPz->new($p)**($e >> 1);
  38. }
  39. else { # power is odd
  40. $prod1 *= $p;
  41. $prod2 *= Math::GMPz->new($p)**(($e - 1) >> 1);
  42. push @prod1_factor_exp, [$p, 1];
  43. }
  44. }
  45. else { # p = 1 (mod 4)
  46. $prod1 *= Math::GMPz->new($p)**$e;
  47. push @prod1_factor_exp, $f;
  48. }
  49. }
  50. $prod1 == 1 and return [$prod2, 0];
  51. $prod1 == 2 and return [$prod2, $prod2];
  52. my @congruences;
  53. foreach my $pe (@prod1_factor_exp) {
  54. my ($p, $e) = @$pe;
  55. my $pp = Math::GMPz->new($p)**$e;
  56. my $key = Math::GMPz::Rmpz_get_str($pp, 10);
  57. my $r = (
  58. $sqrtmod_cache{$key} //= sqrtmod(-1, $pp) // do {
  59. require Math::Sidef;
  60. Math::Sidef::sqrtmod(-1, $pp);
  61. }
  62. );
  63. $r = Math::GMPz->new("$r") if ref($r);
  64. push @congruences, [[$r, $pp], [$pp - $r, $pp]];
  65. }
  66. my @square_roots;
  67. forsetproduct {
  68. push @square_roots, Math::GMPz->new(chinese(@_));
  69. } @congruences;
  70. my @solutions;
  71. foreach my $r (@square_roots) {
  72. my $s = $r;
  73. my $q = $prod1;
  74. while ($s * $s > $prod1) {
  75. ($s, $q) = ($q % $s, $s);
  76. }
  77. push @solutions, [$prod2 * $s, $prod2 * ($q % $s)];
  78. }
  79. foreach my $pe (@prod1_factor_exp) {
  80. my ($p, $e) = @$pe;
  81. for (my $i = $e % 2 ; $i < $e ; $i += 2) {
  82. my @factor_exp;
  83. foreach my $pp (@prod1_factor_exp) {
  84. if ($pp->[0] == $p) {
  85. push(@factor_exp, [$p, $i]) if ($i > 0);
  86. }
  87. else {
  88. push @factor_exp, $pp;
  89. }
  90. }
  91. my $sq = $prod2 * Math::GMPz->new($p)**(($e - $i) >> 1);
  92. push @solutions, map {
  93. [map { $_ * $sq } @$_]
  94. } __SUB__->(\@factor_exp);
  95. }
  96. }
  97. return @solutions;
  98. };
  99. my @factor_exp = factor_exp($n);
  100. my @solutions = $find_solutions->(\@factor_exp);
  101. @solutions = sort { $a->[0] <=> $b->[0] } do {
  102. my %seen;
  103. grep { !$seen{$_->[0]}++ } map {
  104. [sort { $a <=> $b } @$_]
  105. } @solutions;
  106. };
  107. return @solutions;
  108. }
  109. # Run some tests
  110. use Test::More tests => 6;
  111. is_deeply([sum_of_two_squares_solutions(2025)], [[0, 45], [27, 36]],);
  112. is_deeply([sum_of_two_squares_solutions(164025)], [[0, 405], [243, 324]]);
  113. is_deeply([sum_of_two_squares_solutions(99025)], [[41, 312], [48, 311], [95, 300], [104, 297], [183, 256], [220, 225]]);
  114. is_deeply(
  115. [grep { my @arr = sum_of_two_squares_solutions($_); @arr > 0 } -10 .. 160],
  116. [0, 1, 2, 4, 5, 8, 9, 10, 13, 16, 17, 18, 20, 25, 26, 29, 32, 34, 36, 37, 40, 41,
  117. 45, 49, 50, 52, 53, 58, 61, 64, 65, 68, 72, 73, 74, 80, 81, 82, 85, 89, 90, 97, 98, 100,
  118. 101, 104, 106, 109, 113, 116, 117, 121, 122, 125, 128, 130, 136, 137, 144, 145, 146, 148, 149, 153, 157, 160
  119. ]
  120. );
  121. do {
  122. use bigint try => 'GMP';
  123. is_deeply(
  124. [sum_of_two_squares_solutions(Math::GMPz->new("11392163240756069707031250"))],
  125. [[39309472125, 3374998963875],
  126. [216763660575, 3368260197225],
  127. [477329304375, 3341305130625],
  128. [729359177085, 3295481517405],
  129. [735019741071, 3294223614297],
  130. [907262616645, 3251005657515],
  131. [982736803125, 3228992353125],
  132. [1151205969375, 3172835964375],
  133. [1224793301193, 3145162095999],
  134. [1393801568775, 3074000720175],
  135. [1622919634875, 2959441687125],
  136. [1847545189875, 2824666354125],
  137. [1993551800625, 2723584854375],
  138. [2056446956025, 2676413487825],
  139. [2194367046795, 2564549961435],
  140. [2198769707673, 2560776252111],
  141. [2386646521875, 2386646521875]
  142. ]
  143. );
  144. is_deeply([sum_of_two_squares_solutions(2**128 + 1)],
  145. [[1, 18446744073709551616], [8479443857936402504, 16382350221535464479]]);
  146. is_deeply(
  147. [sum_of_two_squares_solutions(13**18 * 5**7)],
  148. [[75291211970, 2963091274585],
  149. [100083884615, 2962357487570],
  150. [124869548830, 2961416259815],
  151. [149646468985, 2960267657230],
  152. [154416779750, 2960022656375],
  153. [179181003625, 2958626849750],
  154. [203932680250, 2957023863625],
  155. [228670076375, 2955213810250],
  156. [253391459750, 2953196816375],
  157. [258150241063, 2952784638466],
  158. [282850264814, 2950521038023],
  159. [307530481817, 2948050825694],
  160. [332189163826, 2945374174457],
  161. [356824584103, 2942491271746],
  162. [481345955350, 2924702504425],
  163. [505803171575, 2920572173350],
  164. [530224968650, 2916237327575],
  165. [554609636425, 2911698270650],
  166. [578955467350, 2906955320425],
  167. [583639307225, 2906018552450],
  168. [607936593550, 2901032879225],
  169. [632191308775, 2895844059550],
  170. [656401754450, 2890452456775],
  171. [680566235225, 2884858448450],
  172. [802350873038, 2853386013959],
  173. [826200069721, 2846571993278],
  174. [849991411282, 2839558639801],
  175. [873723231719, 2832346444642],
  176. [897393869198, 2824935912839],
  177. [901945120375, 2823486084250],
  178. [925540625750, 2815839700375],
  179. [949071319625, 2807996135750],
  180. [972535554250, 2799955939625],
  181. [977046452345, 2798385051790],
  182. [1000429281410, 2790111094745],
  183. [1023742054855, 2781641758610],
  184. [1046983140190, 2772977636455],
  185. [1070150909945, 2764119334990],
  186. [1186462080890, 2716226499895],
  187. [1209150070505, 2706203018090],
  188. [1231753388710, 2695990032905],
  189. [1254270452695, 2685588259510],
  190. [1276699685690, 2674998426295],
  191. [1281008818375, 2672937536750],
  192. [1303331253250, 2662124398375],
  193. [1325562421625, 2651124843250],
  194. [1347700766750, 2639939641625],
  195. [1369744738375, 2628569576750],
  196. [1373978929622, 2626358804329],
  197. [1395908335991, 2614769317862],
  198. [1417739993098, 2602996730711],
  199. [1439472372169, 2591041867258],
  200. [1461103951382, 2578905564649],
  201. [1569204922025, 2514592328950],
  202. [1590192225050, 2501373094025],
  203. [1611068173975, 2487978699050],
  204. [1631831306950, 2474410081975],
  205. [1652480170025, 2460668192950],
  206. [1656443419150, 2458002007175],
  207. [1676954116825, 2444054737150],
  208. [1697347384850, 2429936320825],
  209. [1717621795175, 2415647746850],
  210. [1838087734327, 2325298292486],
  211. [1857481600234, 2309835659287],
  212. [1876745394953, 2294211278554],
  213. [1895877769526, 2278426244393],
  214. [1899547017625, 2275368057250],
  215. [1918520912750, 2259392877625],
  216. [1937360462375, 2243259482750],
  217. [1956064347250, 2226969002375],
  218. [1974631257625, 2210522577250],
  219. [1978190975930, 2207337566135],
  220. [1996592834665, 2190706671530],
  221. [2014854880870, 2173922371465],
  222. [2032975835735, 2156985841270],
  223. [2050954430330, 2139898266935]
  224. ]
  225. )
  226. if 0;
  227. };
  228. my @nums = (@ARGV ? (map { Math::GMPz->new($_) } @ARGV) : (map { int rand(~0) } 1 .. 20));
  229. foreach my $n (@nums) {
  230. (my @solutions = sum_of_two_squares_solutions($n)) || next;
  231. say "$n = " . join(' = ', map { "$_->[0]^2 + $_->[1]^2" } @solutions);
  232. # Verify solutions
  233. foreach my $solution (@solutions) {
  234. if ($n != $solution->[0]**2 + $solution->[1]**2) {
  235. die "error for $n: (@$solution)\n";
  236. }
  237. }
  238. }
  239. __END__
  240. 999826 = 99^2 + 995^2 = 315^2 + 949^2 = 525^2 + 851^2 = 699^2 + 715^2
  241. 999828 = 318^2 + 948^2
  242. 999844 = 312^2 + 950^2 = 410^2 + 912^2
  243. 999848 = 62^2 + 998^2
  244. 999850 = 43^2 + 999^2 = 321^2 + 947^2 = 565^2 + 825^2
  245. 999853 = 387^2 + 922^2
  246. 999857 = 401^2 + 916^2 = 544^2 + 839^2
  247. 999860 = 154^2 + 988^2 = 698^2 + 716^2
  248. 999869 = 262^2 + 965^2 = 613^2 + 790^2
  249. 999881 = 341^2 + 940^2 = 484^2 + 875^2
  250. 999882 = 309^2 + 951^2 = 651^2 + 759^2
  251. 999890 = 421^2 + 907^2 = 473^2 + 881^2
  252. 999892 = 324^2 + 946^2
  253. 999898 = 213^2 + 977^2 = 697^2 + 717^2
  254. 999909 = 222^2 + 975^2 = 678^2 + 735^2
  255. 999914 = 667^2 + 745^2
  256. 999917 = 109^2 + 994^2
  257. 999937 = 44^2 + 999^2 = 89^2 + 996^2
  258. 999938 = 77^2 + 997^2
  259. 999940 = 126^2 + 992^2 = 178^2 + 984^2 = 306^2 + 952^2 = 448^2 + 894^2 = 578^2 + 816^2 = 696^2 + 718^2
  260. 999941 = 370^2 + 929^2 = 446^2 + 895^2
  261. 999944 = 638^2 + 770^2
  262. 999946 = 585^2 + 811^2
  263. 999949 = 243^2 + 970^2 = 290^2 + 957^2 = 450^2 + 893^2 = 493^2 + 870^2
  264. 999952 = 444^2 + 896^2
  265. 999953 = 568^2 + 823^2
  266. 999954 = 327^2 + 945^2 = 375^2 + 927^2
  267. 999956 = 500^2 + 866^2
  268. 999961 = 644^2 + 765^2
  269. 999962 = 239^2 + 971^2 = 541^2 + 841^2
  270. 999968 = 452^2 + 892^2
  271. 999970 = 247^2 + 969^2 = 627^2 + 779^2
  272. 999973 = 63^2 + 998^2 = 118^2 + 993^2 = 273^2 + 962^2 = 442^2 + 897^2 = 622^2 + 783^2 = 658^2 + 753^2
  273. 999981 = 141^2 + 990^2
  274. 999986 = 365^2 + 931^2 = 695^2 + 719^2
  275. 999997 = 194^2 + 981^2 = 454^2 + 891^2
  276. 1000000 = 0^2 + 1000^2 = 280^2 + 960^2 = 352^2 + 936^2 = 600^2 + 800^2