Durango Bill’s
“C” Program to generate Ramanujan Numbers
(Source Code)
Return to the
main Ramanujan page
Web page generated via Sea Monkey's Composer HTML editor
within a Linux Cinnamon Mint 18 operating system.
(Goodbye Microsoft)
//*************************************************************************
//
//
Ramanujans.c
//
by
//
Bill Butler
//
// This program finds Ramanujan numbers. A Ramanujan
number is a number
// formed by the sum of two cubes in 2 or more different
ways. For example:
//
// 12^3 + 1^3 = 9^3 + 10^3 = 1729
//
// There are an infinite number of other paired cubes that
have a common sum.
//
// Also, the number of pairs can be extended to higher
orders.
//
// For example, the first quadruple, Taxicab(4), occurs
at:
//
// 13,322^3 + 16,630^3 =
// 10,200^3 + 18,072^3 =
// 5,436^3 + 18,948^3 =
// 2,421^3 + 19,083^3 =
6,963,472,309,248 ( 6963472309248 )
// (Takes about 30 seconds
using this program on a 3GHz processor)
// (Also nails Taxicab(5)
in less than 3 1/4 hrs.)
//
// Imagine that the following table is an Excel
speadsheet. The first row
// across the top counts upward for J = 1, 2, 3, 4, etc.
The second row gives
// the cube for each of these numbers: 1, 8, 27, 64, etc.
Similarly, the first
// column counts upward for I = 1, 2, 3, 4, etc. and the
2nd column gives the
// cube for each of these numbers: 1, 8, 27, 64, etc. The
rest of the cells in
// the spreadsheet are the sum of two cubes using the
respective rows and
// columns.
//
//
J 1 2
3 4 5
6 7 8
9 10 11 12 13
//
J^3 1 8 27
64 125 216 343 512 729 1000 1331
1728 2197
// I I^3
------------------------------------------------------------------
// 1 1 |
2 9 28 65
126 217 344 513 730 1001 1332 1729 2198
// 2 8 |
9 16 35 72 133
224 351 520 737 1008 1339 1736 2205
// 3 27 |
28 35 54 91 152
243 370 539 756 1027 1358 1755 2224
// 4 64 |
65 72 91 128 189
280 407 576 793 1064 1395 1792 2261
// 5 125 | 126 133
152 189 250 341 468 637 854
1125 1456 1853 2322
// 6 216 | 217 224
243 280 341 432 559 728 945
1216 1547 1944 2413
// 7 343 | 344 351
370 407 468 559 686 855 1072 1343
1674 2071 2540
// 8 512 | 513 520
539 576 637 728 855 1024 1241 1512 1843
2240 2709
// 9 729 | 730 737
756 793 854 945 1072 1241 1458 1729 2060 2457
2926
// 10 1000 | 1001 1008 1027 1064 1125 1216 1343 1512
1729 2000 2331 2728 3197
// 11 1331 | 1332 1339 1358 1395 1456 1547 1674 1674
1843 2060 2331 2662 3059
// 12 1728 | 1729 1736 1755 1792 1853 1944 2071 2240
2457 2728 3059 3456 3925
// 13 2197 | 2198 2205 2224 2261 2322 2413 2540 2709
2926 3197 3528 3925 4394
//
// The table can be extended for as many rows and columns
as needed. The lower
// left triangle of the table is a mirror image of the
upper right triangle.
// Thus, the lower left triangle can be ignored.
//
// Within the upper right triangle, we want to look for
two identical numbers.
// After some searching, we note that the number
1729 appears twice. It is
// found at row 1, column 12; and again at row 9, column
10. Thus 1^3 + 12^3
// equals 9^3 + 10^3 equals 1729. Hence, 1729 is a
Ramanujan number. If we
// infinitely increased the size of the table, we could
find an infinite
// number of other paired cube sums. Also, we could find
an infinite number of
// "triples". The bad news is that the first of these
"Ramanujan triples"
// occurs at:
// (Row 228, Column 423), (Row 167, Column 436), and (Row
255, Column 414)
// for 228^3 + 423^3 = 167^3 + 436^3
= 255^3 + 414^3, which all share a
// common cell entry of 87,539,319.
//
// If we are to extend the process of finding additional
Ramanujan triples or
// even higher order matches (e.g. Ramanujan quadruples),
three major problems
// become apparent. 1) The table will become very large.
2) The search time
// will become prohibitive. 3) The size of the numbers in
the cells will
// run into precision problems. (Too many digits for
standard computer
// hardware.)
//
// We note that numbers in the table are confined to
specific areas. Numbers
// from 1 to 1,000 appear only in the upper left corner.
Numbers between 1,000
// and 2,000 occur in a band that extends from upper right
to lower left.
// Numbers between 2,000 and 3,000 exist only in another
band that begins off
// the right side of the table and extends down to the
left. If we are looking
// for possible number matches, we only have to look at
one band at a time.
//
// The program sequentially generates these bands. Each
new band is used for
// the current search process. Then this old band is
discarded and a new band
// is generated. The problem then is to search all of the
numbers within a
// band to see if duplicates exist. The example above uses
a band width of
// 1,000 for the search area. The program initially uses
10 billion for
// the band width (for 4 & 5 pair combinations), but
since the density of the
// I^3 + J^3 sums decreases with larger numbers, this band
width is allowed
// to expand with time.
//
// The program generates a little over 2.4 million numbers
at a time for each
// new band. (Stabilizes at this rate after a few dozen
iterations.) These
// numbers are inserted into a hash table, and then the
hash table is searched
// to see if duplicate entries exist. If there are enough
entries for any hash
// number, then the associated link list of results for
this hash number are
// searched for actual solutions.
//
// The algorithm (especially the hash index portion) is
very efficient and
// appears to be at least 10 times faster than the "heap"
algorithm used by
// David Wilson who published the first Ramanujan
quintuple.
//
// If the search is continued to include the lowest known
Ramanujan sextuple,
// (= Taxicab(6)), numbers in the table would have 23
decimal digits. No
// simple data representation (or standard computer
hardware) has this much
// precision. Thus numbers are split into two pieces. One
part includes the
// rightmost 9 decimal digits and is maintained as "int"
variables. The
// remaining leftmost 14 decimal digits are maintained as
"double" variables.
// The integer portion does double duty as it is used to
construct the hash
// indexes.
//
// The program has been updated from an old ANSI "C"
version that ran under
// DOS on a 80486 computer. (And that was a copy of a
version that ran on a
// 80386 computer, and that was a copy of the original
that I wrote for a
// 32032 32-bit National Semiconductor co-processor board
that I added to an
// IBM PC-XT back in the early 80's. (Those were the days
when you sold your
// soul for 1 MB of direct RAM addressing - no PC/DOS
segmentation. Anybody
// remember what "EDLIN" was?)
//
// This program may be used, copied, modified, etc.
without any obligation by
// any person for any not-for-profit purpose. I would
appreciate that this or
// any derivative version would include a note crediting
me with the original
// program/algorithm. (e.g. "Original algorithm by Bill
Butler.")
//
// Final note: The program runs best if you have >= 1
GB of RAM. It runs as is
// under Windows XP when compiled by the lcc-win32 C
compiler as a "console"
// program.
//
//***************************************************************************
#include <stdheaders.h>
// The usual stdio.h, stdlib.h, etc
// "ArrayLimit" controls how far you want to search. The
program
// will search for Ramanujans up to ArrayLimit^3. (No
other
// modifications to the code are needed.)
// DO NOT use anything > 30000000.
// At 30,000,000 for ArrayLimit, the author was able to
run time
// trials up to 2.15E22, and successfully run a test to
see if it
// would find the best known candidate for Taxicab(6).
(Started a
// short distance lower than this.) However, when the
program was
// running with "ArrayLimit" at 30,000,000 at the same
time that
// several other programs were running, the author's
computer
// (1 GB RAM) crashed and corrupted Norton's "Go Back"
files.
// Norton's "System Works" had to be uninstalled and then
// reinstalled. (System "Commit Charge" was not known, but
an
// overload is possible.)
#define ArrayLimit 10000000 //
Program will crash if/when search passes
// ArrayLimit^3
// Procedure
declarations
void InitSys(void);
// Initialize the program
void NextGroup(void);
// Generate next band of numbers
void CheckGroup(void);
// Search for duplicate entries
void MakeCube(int);
// Cubes the integer and places
the results
// in global
variables CubedHigh and CubedLow
void pausemsg(void);
// Pauses the program at key points
// Note: Each
10000000 increase in ArrayLimit
// uses another 160
MB of RAM. ArrayLimit of
// 30000000 is good
enough for Taxicab(6).
// (But estimated
run time is 7 years.)
double NcubedHigh[ArrayLimit+1];// NcubedHigh[i] =
leftmost 14 digits of i^3
int NcubedLow[ArrayLimit+1]; //
NcubedLow[i] = 9 rightmost digits of i^3
// These arrays are
used to look up the cubes
// of numbers when
forming the I^3 + J^3 sums
int NextJ[ArrayLimit+1];
// Keeps track of the next "J" to try when
// forming trial I^3
+ J^3.
// The size of the
next 2 arrays matches
// the 22 bit hash
size. Link lists for
// the data arrays
below start here.
unsigned HashHd[4194304];
// Link heads for hash table.
int ChainLen[4194304];
// Chain length.
// As trial I^3 +
J^3 numbers are generated
// for each new
"band"/group, they are
// stored in these
link lists. (Purists might
// want to use a
structure array, but time
// trials for both
encoding methods showed
// that simple
arrays execute faster.)
double I3J3High[3000000];
// Leftmost 14 digits of I^3 + J^3
int I3J3Low[3000000];
// 9 rightmost digits of I^3 + J^3
int Ival[3000000];
// The "I" in I^3 + J^3
int Jval[3000000];
// The "J" in I^3 + J^3
int Link[3000000];
// Link lists.
double CubedHigh;
//
The MakeCube() routine is passed an integer
int CubedLow;
//
number. It cubes this number and places the
// lowest
(rightmost) 9 decimal digits in
// CubedLow and the
remaining digits in
// CubedHigh. These
are then copied into the
// Ncubed arrays.
char Databuff[100];
// Dummy array for user input.
int NbrPairs;
// User
input for minimum number of pairs
// required for
output display. (=2,3,4,or 5)
double InitValues[10] = {
// Initial upper limit & increment. Will keep
0.0, 0.0, 2000.0,
// output display in
roughly sorted order.
20000000.0,
10000000000.0,
10000000000.0 };
double UpperLim;
// For each new
group, find cube sums up to
// this limit.
UpperLim increases by
double Increment;
// "Increment" for
each new group. In turn,
// "Increment"
intermittently increases as
// array storage
allows. "Increment" is the
// same as the "band
width" phrase used in
// the Excel
Spreadsheet example.
int NbrStored;
// Nbr.
of items in hash table/I3J3 arrays.
unsigned Bit22Mask = 4194303; // Mask for 22
bit index into Hash Table
int StatusFlag;
// Set by user
for display/no-display of
// status data while
program runs.
int main(void) {
InitSys();
// Initialize system.
while(1) {
// Do until user stops the program
NextGroup();
// Form next group of cube sums.
CheckGroup();
// Look for matches.
// Process a
little over 2,400,000
if (NbrStored < 2400000)
// trial I^3 + J^3 each iter.
Increment *= 1.05;
// Increases by 5 %
as array space allows
UpperLim += Increment;
// Set up for next group
if (StatusFlag) {
//
Optional status check
printf("Upper Limit is now %g\n",
UpperLim);
printf("Increment is now %g\n",
Increment);
}
}
return 0;
}
//****************************************************************************
//
//
InitSys
//
// This routine initializes the system. The Ncubed[]
arrays are filled with
// cubes such that NcubedHigh[i]*E9 + NcubedLow[i] = I^3.
23+ digits of
// precision can be handled.
//
// Note: The program allows you to begin your search at
any arbitrary
// starting point. Thus, 2 or more computers could each be
running the program
// to simultaneously search different zones in the number
field.
//
// The NcubedLow[] array contains the integer bit version
of the last 9
// decimal digits of all the I^3's. These will be used to
form a hash index.
// The hash index system greatly speeds up the process of
finding matching
// pairs of I^3 + J^3 = K^3 + L^3, etc. (The actual hash
index adds the bit
// portions of I^3 + J^3, and uses bits 8 to 29 of the
result as the hash
// index. Note: The least significant bit is bit "0".)
//
// The NextGroup() routine will generate a large group of
trial I^3 + J^3
// numbers. The "J's" that will be used in this routine
will be >= "I" until
// the sum of I^3 + J^3 reaches the upper limit for the
current group. (Group
// size is kept within bounds by stopping the process at
"UpperLim".). This
// routine initializes the NextJ[] array for this process.
//
// "UpperLim" and "Increment" are initially set to 10
billion for the first
// iteration. (Valid for 4 & 5 pairs. Lower values for
pairs = 2 & 3).
// Subsequently "UpperLim" will be increased by
"Increment" to include larger
// trial values of I^3 + J^3. The density of I^3 + J^3
numbers becomes sparser
// as the number field expands. Therefore, "Increment" is
allowed to become
// larger with time. Thus, the number of trial I^3 + J^3
numbers that will be
// looked at will gradually cluster at a little over 2.4
million per
// iteration.
//
//**************************************************************************
void InitSys(void) {
int i;
double start, Difference, Idbl, Jcalc;
puts("
ramanujans.c");
puts("
by");
puts("
Bill
Butler\n");
puts("This program finds Ramanujan numbers.");
puts("It will run until you manually stop the
program.\n");
puts("The number that you enter below will determine how
many pairs");
puts("are required to display the associated Ramanujan
number.\n");
puts("Enter 2 for output of 2 or more pairs (Pauses on 3
pairs)");
puts("Enter 3 for output of 3 or more pairs (Pauses on 4
pairs)");
puts("Enter 4 for output of 4 or more pairs (Pauses on 5
pairs)");
puts("Enter 5 for output of 5 or more pairs (Pauses on 6
pairs)");
puts("(For output from 2, 3, 4 use \"Pause\" key as
needed)");
gets(Databuff);
NbrPairs = atoi(Databuff);
start = ArrayLimit;
// Convert to double for
calc
printf("\nWhere do you want to start the search? (0 <=
start <= %g)\n",
0.999999 * start * start *
start);
puts("(Suggest a very small percent below true start
point if > 0.)");
gets(Databuff);
start = atof(Databuff);
puts("\nDo you want to display status results (1 for
yes 0 for no)?");
puts("(If yes, don't forget that real data may scroll off
the screen.)");
gets(Databuff);
StatusFlag = atoi(Databuff);
puts("\nInitializing the cubes table");
puts("(Will take a few seconds.)\n");
for (i = 1; i <= ArrayLimit; i++) {
MakeCube(i);
// Calculates i^3
NcubedHigh[i] = CubedHigh;
// Leftmost 14 digits
NcubedLow[i] = CubedLow;
// Rightmost 9 digits
// Initialize NextJ[]
Idbl = i;
// Convert to double
Difference = start -
Idbl*Idbl*Idbl; //
Derived from nbr = I^3+J^3
if (Difference < 0.0)
// If calc will be
negative
NextJ[i] = i;
// then ordinary start point
else {
// Else calc which J to use
Jcalc = pow(Difference, 1.0/3.0) +
1.0; // Calculated value for J.
// Adding 1.0 avoids an array
// overflow problem
if (Jcalc > Idbl)
// If this is
bigger than "i"
NextJ[i] =
Jcalc;
// use
the calculated value
else
// Else
NextJ[i] =
i;
// use the normal value.
}
/* Optional debug check
printf("At i = %d NcubedHigh = %'.0lf *
E9 NcubedLow = %'d NextJ = %d\n",
i, NcubedHigh[i],
NcubedLow[i], NextJ[i]);
*/
}
UpperLim = InitValues[NbrPairs] +
start; // Initial upper limit.
Increment = InitValues[NbrPairs];
// Initial
increment.
puts("Starting search\n");
}
//*************************************************************************
//
//
NextGroup
//
// This routine generates the next group of I^3 + J^3 sums
and places these
// sums in the hash arrays. The number of these trial "I^3
+ J^3" sums that
// are processed will average a little over 2.4 million.
//
//*************************************************************************
void NextGroup(void) {
int Count, i, j;
unsigned i3j3sumLow;
// Does
double duty as hash index
double LimNbr, i3j3sumHigh;
// Forces "LimNbr" to a
"Register"
// position. Otherwise "UpperLim"
// could be used.
for (i = 4194303; i >= 0; i--) {
// Clear old garbage.
HashHd[i] = 0;
ChainLen[i] = 0;
}
Count = 0;
// Count nbr. of
I^3+J^3 in the group
// and where to put data
// Note: Usage of the "i" & "j"
vars.
// in this loop matches the "I" &
"J"
// row/col labels in the "Excel
// Spreadsheet" example.
i = 1;
// First "I" for I^3 (Start on row 1)
LimNbr = UpperLim;
// Sets up reg. variable
do {
// Do for all "i" in group (Will move
//
down until I^3 + J^3 is too big)
j = NextJ[i];
// First "J" for J^3
(Leftmost column
//
for current row in current group)
// Do for all "J" such that
while(1) {
// I^3 + J^3
is < UpperLim
i3j3sumHigh = NcubedHigh[i] +
NcubedHigh[j]; // Forms I^3 + J^3
i3j3sumLow = NcubedLow[i] +
NcubedLow[j];
if (i3j3sumLow >= 1000000000)
{ // If carry overflow (>= 1 billion),
i3j3sumLow -=
1000000000; // decrease
integer portion by 1e9
i3j3sumHigh +=
1.0;
// and increment the "billions"
}
// If within upper limit then
if ((i3j3sumHigh * 1.0E9 + i3j3sumLow)
< LimNbr) {
Count++;
// include it in the
group.
I3J3High[Count] =
i3j3sumHigh; // Add to the hash arrays.
I3J3Low[Count] =
i3j3sumLow;
Ival[Count] = i;
Jval[Count] = j;
i3j3sumLow >>=
8;
// Generate the
i3j3sumLow &=
Bit22Mask; // hash
index
Link[Count] =
HashHd[i3j3sumLow]; // Update link list
HashHd[i3j3sumLow] =
Count;
ChainLen[i3j3sumLow]++;
j++;
// Move 1 col to right in
}
// "Excel spreadsheet"
else
// Repeat until too big
break;
}
NextJ[i] = j;
// Set up for
next group. Move down
i++;
// 1 row in "Excel spreadsheet".
} while (j > i);
// Loop exits when right edge of
// band/group intercepts the
sloping
// diagonal in the spreadsheet.
At
// this point, "i" will equal
"j".
NbrStored = Count;
// Optional status check.
// Averages ~2,400,000+ per crack.
if (StatusFlag) {
printf("This iter. stored %'d trial I^3 + J^3
sums\n", NbrStored);
puts("Must stay < 3,000,000\n");
}
}
//************************************************************************
//
//
CheckGroup
//
// This routine checks the hash arrays to see if any
matches exist. If the
// quantity of numbers stored in any hash link list is
>= the user defined
// display number, an actual Ramanujan solution may exist.
If true, the
// link list chain is checked to see if at least
"NbrPairs" entries are
// identical. If true, then the information is output.
//
// The variable "NbrPairs" (see start of program) controls
how many pairs
// have to exist for the Ramanujan number before this info
is output.
//
//************************************************************************
void CheckGroup(void) {
int HashIndex, Ilink, Jlink;
int Count;
int StopNbr;
// For all hash
heads
for (HashIndex = 0; HashIndex <= 4194303; HashIndex++)
{
if ((ChainLen[HashIndex] - NbrPairs) <
0) // Chain is too short for output.
continue;
// Optional check to
see if
// hash algorithm is
efficient.
/*
printf("Link list[%d] has %d members\n",
HashIndex, ChainLen[HashIndex]);
*/
// Check the list
for this hash.
// StopNbr controls
how many times
// that "Ilink"
moves forward
StopNbr = ChainLen[HashIndex] - NbrPairs;
for (Ilink = HashHd[HashIndex]; StopNbr >=
0;
StopNbr--, Ilink = Link[Ilink]) {
Count = 1;
// Will count number of identical
// I3J3 sums
for (Jlink = Link[Ilink]; Jlink; Jlink
= Link[Jlink]) {
if (I3J3Low[Jlink] !=
I3J3Low[Ilink]) // Not a match if either
is
continue;
// different.
if (I3J3High[Jlink] !=
I3J3High[Ilink])
continue;
// If to here, the I3J3 sum at Jlink
// matches the I3J3 sum at Ilink
Count++;
}
if (Count <
NbrPairs)
// If not
enough pair matches,
continue;
// then keep looking.
printf("\n%d pairs exist at Ramanujan
Number: %'.0lf * E+9 + %'d\n",
Count, I3J3High[Ilink],
I3J3Low[Ilink]);
for (Jlink = Ilink; Jlink; Jlink =
Link[Jlink]) {
if (I3J3Low[Jlink] !=
I3J3Low[Ilink]) // Both must match for a
continue;
// paired
solution
if (I3J3High[Jlink] !=
I3J3High[Ilink])
continue;
printf(" Pair
at I = %'d J = %'d\n", Ival[Jlink],
Jval[Jlink]);
}
if (Count >
NbrPairs)
// Pause if
big match
pausemsg();
}
}
}
//*************************************************************************
//
//
MakeCube
//
// This routine cubes the passed integer number and places
the lowest 9
// decimal digits of the result in the global integer
variable "CubedLow"
// and the high 14 digits into CubedHigh (double).
Subsequently these
// results are loaded into the Ncubed arrays.
//
// The multiplication process is similar to ordinary base
10 arithmetic
// (and its associated "carry") except base 10,000,000 is
used.
//
// The passed number "AnInteger" is known to be <= 3E7.
This is converted
// to a "double" number which can be safely squared
without any precision
// problems. This result is then split into three seperate
sections that
// have the equivalent of 7 decimal digits each. Each of
these can be safely
// multiplied by the original "AnInteger" again without
any precision error.
//
// Finally, the end result is placed into two variables
with the lowest 9
// decimal digits stored in one variable and the high 14
digits in the other.
// These two variables are then placed in the global
variables "CubedHigh"
// and "CubedLow". (The latter becomes an integer
variable.)
//
//**************************************************************************
void MakeCube(int AnInteger) {
double HighDigits, MidDigits, LowDigits, temp, Original;
// "AnInteger" will be <= 3E7. Thus
// there is enough precision in an
// ordinary "double" variable for
// the first multiplication.
Original = AnInteger;
// Convert to
"double"
temp = Original * Original;
// Initially this will be <= 9E14
// Split it into high, mid, low digits
LowDigits = fmod(temp, 10000000.0); //
These will be the low 7 digits
MidDigits = (temp - LowDigits) /
10000000.0; // This is the "carry"
// Now reduce MidDigits to
7 digits
temp = fmod(MidDigits, 10000000.0); //
This will be the middle 7 digits
HighDigits = (MidDigits - temp) /
10000000.0; // Another "carry"
MidDigits = temp;
// At
this point "AnInteger" has been
// been squared. The lowest 7 digits
are
// in "LowDigits", the next 7 digits
are
// in "MidDigits", and the remaining
// digits are in "HighDigits".
LowDigits *= Original;
// Mult. all three
by "Original" to get
MidDigits *= Original;
// cube. Then
will have to sort out the
HighDigits *= Original;
// pieces. After this
multiplication, the
// true cubed
result would be:
// HighDigits*E14+MidDigits*E7+LowDigits
temp = fmod(MidDigits, 100.0);
// Get last 2 digits from MidDigits
MidDigits -= temp;
// Remove
last two digits
MidDigits /= 100.0;
// Shift
right two decimal digits
// so it measures "Billions"
LowDigits += 10000000.0 *
temp; // All 9 low
digits are in LowDigits
// (But still has some higher digits)
temp = fmod(LowDigits, 1000000000.0); // These are
the final lowest 9 digits
MidDigits += (LowDigits - temp) /
1000000000.0; // Add carry to Mid Digits
MidDigits += 100000.0 * HighDigits; //
The 14 high digits are in place
CubedHigh = MidDigits;
// Store
final result
CubedLow = temp;
// Also converts it to an
integer
}
//******************************************************************
//
//
Misc. Routines
//
//******************************************************************
void pausemsg(void) {
puts("\nLarge number of pairs exist");
puts("Press RETURN to continue");
gets(Databuff);
}