Improved handling of implementation limitations.
Improved user interface shows more digits on-screen and allows long strings not to be generated.
Code:
LOCAL CRID:="REPEATING DECIMAL V 0.8 \n©2018 StephenG1CMZ";
LOCAL ALGM:="Following Joe Horn's algorithm: http://www.hpmuseum.org/forum/thread-9919.html";
//Written 2018 StephenG1CMZ
//Following Joe Horn`s algorithm using Multiplicative Order
//This ppl implement yields useful results using CAS
//to provide Transients and Repetends>12 digits
LOCAL FR2,FR5;//GLOBAL RETURN
LOCAL WHATSLEFT,NDIGITS;//GLOBAL RETURN
LOCAL ImpPRINT:=2000;//PRINT LIMIT <2K
LOCAL ImpSTRING:=64000;//LIMIT VALUE TBC
EXPORT TransientLEN;//Length of Transient
EXPORT RepetendLEN;//Length of Recurring Digits/Repeating Digits
//^Len == Decimal Digits Only
EXPORT USCORE:="_";//CHANGE TO EMPTY STRING IF YOU WISH TO EVAL THE STRING
EXPORT WAITS:=0;
EXPORT ImpRELEN:=ImpSTRING;//MAX LEN TO GENERATE
EXPORT TESTED:=0;
EXPORT BigT:=0;
EXPORT BigR:=0;
LOCAL STNAN:="(NaN)";
LOCAL DIVBY0:=STNAN+"{0}";//DIVBY0
LOCAL NOTINT:=STNAN+"(NotInteger)";
LOCAL STNANImp:=STNAN+"(Imp)";//Implemenation Limit
LOCAL ImpLimST:="MultiplicativeOrder10: Implementation Limit!\n";
MultiplicativeOrder10();//FORWARD
EXPORT ABOUT()
BEGIN
PRINT();
PRINT(CRID);
PRINT(ALGM);
END;
EXPORT HELP()
BEGIN
PRINT();
PRINT(CRID);
PRINT(" Discovers characteristics of rational fractions that can be specified by an integer numerator and denominator.");
PRINT(" ");
PRINT("NB Fractions are input as 2 integers.\nDo not enter as Prime fractions.");
PRINT(" ");
PRINT("Scroll or Esc...");
PRINT(" ");
PRINT("API functions:");
PRINT(" ");
PRINT("GetLEN(fraction):");
PRINT("Returns lengths and Sets LENgth variables.\nLengths exclude the IP and non-digits.");
PRINT("GetLEN(1,3)={0,1}");
PRINT(" ");
PRINT("Recurring(fraction):");
PRINT("Repeating(fraction):");
PRINT("Returns a string of the real value with _ marking any recurring digits.");
PRINT("Recurring(1,3)=0._3");
PRINT(" ");
PRINT("MultiplicativeOrder10(WhatsLeft):");
PRINT("Implemented for base 10.");
PRINT(" ");
PRINT("Procedures:");
PRINT(" ");
PRINT("Show(fraction):");
PRINT("Show Representations on_screen");
PRINT("[Esc] to continue to next page");
PRINT("[View] more digits");
PRINT("Show(1,3)");
PRINT(" ");
PRINT("ShowRange(Numer1,Numer2,Denom1,Denom2):");
PRINT("Show Range of fractions one by one\nShowRange(1,0,3,0) = Show(1,3)");
PRINT("ShowRange(1,2,3,4)=Show(1,3),Show(1,4),Show(2,3),Show(2,4)");
PRINT(" ");
PRINT("ShowReciprocals(Denom1,Denom2):");
PRINT("Shows a range of reciprocal values one by one.\nShowReciprocals(3,0)=Show(1,3)");
PRINT("ShowReciprocals(1,3)=Show(1,1),Show(1,2),Show(1,3)");
PRINT("");
PRINT("Variables In: (Selectable by user @Home)");
PRINT("ImpRELEN: Recurring LEN limit");//Limit to manageable length
PRINT("String USCORE: Underscore ");
PRINT("WAITS: Show WAIT time (0=Esc)");
PRINT(" ");
PRINT("Variables Out:");
PRINT("RepetendLEN, TransientLEN");
PRINT(" ");
PRINT("Known Limitations:");
PRINT("Numbers beyond about 1/12109:");
PRINT("Repetend digits incorrectly show just 0-s (LEN OK)");
PRINT(" ");
PRINT("Hint: To PRINT long strings,");
PRINT("PRINT(a few bytes at a time)");
PRINT(" ");
PRINT("Interesting Test Values:");
PRINT("{1/: 0,1,2,3,4,7,14,208,301,8192}");
PRINT("{1/: 29989}//WRONG}");
PRINT("{1/: 3^15}//CRASH AVOIDED");
PRINT("{2/: 16384}")
END;
//STANDARD ROUTINES
DecSepNow()
//RETURN current system decimal separator
//From the forum
BEGIN
RETURN IFTE((HSeparator MOD 9)<4,".",",");
END;
//QUERY:WOULD TAKING LOGS OF NUMERATOR AND DENOMINATOR AND SUBTRACTING
//IMPROVE RANGE OR PRECISION TBD
DIGITSNEEDED(NN)
//DIGITS NEEDED FOR IP
//EXCLUDES SIGN POINT SEPARATOR
BEGIN
IP(LOG(MAX(1,ABS(NN))))+1;
END;
ZEROS(NN)
//Recursive is faster than using CAS
//and can output more zeros than PPL
BEGIN
//MSGBOX(NN);
IF NN>0 THEN
RETURN "0"+ZEROS(NN-1);
END;
RETURN "";
END;
//END STANDARD
MaxFactor25(NN)
BEGIN
LOCAL MAXFACTR:=0;
LOCAL LST,LSTB,LP,II;
LST:=mat2list(ifactors(NN)); //BASES AND COUNTSETC
//EXTRACT BASES (2,5) ETC
LSTB:=IFTE(SIZE(LST),MAKELIST(LST(2*II-1),II,1,(SIZE(LST)/2)),{});//{} HANDLES NN=1
FR2:=0; FR5:=0;
LP:=POS(LSTB,2);
IF LP THEN
FR2:=LST(2*LP);//EXPONENT
END;
LP:=POS(LSTB,5);
IF LP THEN
FR5:=LST(2*LP);//EXPONENT
END;
MAXFACTR:=MAX(FR2,FR5);
RETURN MAXFACTR;//0=NONE
END;
EXPORT GetLEN(NumerN,DenomN)
//Get TransientLEN,RepetendLEN,NDIGITS
//These lengths are decimal places and exclude IPLEN
//0 DENOMINATOR: RETURN {0,0,0}
BEGIN
LOCAL GD;
LOCAL RedNUMER,RedDENOM;
IF DenomN THEN
//Step 0: Reduce Fraction
GD:=gcd(NumerN,DenomN);
RedNUMER:=NumerN/GD;
RedDENOM:=DenomN/GD;
//Continue
TransientLEN:=MaxFactor25(RedDENOM);
WHATSLEFT:=exact(RedDENOM/(2^FR2*5^FR5));
RepetendLEN:=MultiplicativeOrder10(WHATSLEFT);
NDIGITS:=TransientLEN+RepetendLEN;
//PRINT({TransientLEN,RepetendLEN});
ELSE //DIVBY0
TransientLEN:=0;
RepetendLEN:=0;
NDIGITS:=0;
END;
RETURN {TransientLEN,RepetendLEN,NDIGITS};
END;
ReDIGITS (NUMER,NN)
//Determine Digits of the fraction (NUMER/NN)
//NUMERATOR INTEGER
//DENOMINATOR INTEGER
//NEGATIVES OK
//DENOMINATOR 0: RETURN NAN
//NONINTEGER: UNCHECKED
BEGIN
LOCAL GD;
LOCAL LST;//UNUSED
LOCAL ST:="";//STRINGVERSION
LOCAL STR:="";
LOCAL TRANSIENTPART,REPETEND,INTPART;
LOCAL RESULTS;
LOCAL DP;//DECIMAL PLACES
LOCAL IPLEN;
LOCAL RedNUMER,RedDENOM;
//MSGBOX({NUMER,NN});
//GUARDS
IF NN==0 THEN
RETURN DIVBY0;
END;
IF FP(NUMER) OR FP(NN) THEN
RETURN NOTINT+STRING({NUMER,NN});//PARAMETERS NOT INTEGER
END;
IF (NUMER/NN)<0 THEN
RETURN "−"+ReDIGITS(ABS(NUMER),ABS(NN));
END;
//OK
//Step 0: reduce fraction
GD:=gcd(NUMER,NN);
RedNUMER:=NUMER/GD;
RedDENOM:=NN/GD;
//Continue...
//Get Length
//A possible optimisation:
//Avoid recalc
LST:=GetLEN(RedNUMER,RedDENOM);
//IF EXCESSIVE LENGTH RETURN NAN
IF NDIGITS>MIN(ImpRELEN,ImpSTRING-9) THEN //−9-ALLOW FOR DECSEP ETC
//STRING:TOO LONG TO GENERATE
//RELEN:USER LIMIT(TOO LONG/SLOW FOR USER)
RETURN STNANImp;
END;
//GetDecimalPlaces
//RP:=iquo((1*10^NDIGITS),NN);//works for repetends≤12digits:so use CAS
CAS("temporary:=iquo(((RedNUMER*10^NDIGITS),RedDENOM)");
ST:=CAS("string(temporary)");
//MSGBOX(ST);
STR:=ZEROS(NDIGITS-DIM(ST));
DP:=STR+ST;
//GetINTEGERPART
INTPART:=IFTE(RedDENOM,(IP(RedNUMER/RedDENOM)+DecSepNow()),DIVBY0);//THIS DIVBY IS GUARDED
//IF NUMER==1 THEN
//RECIPROCAL:iquo returns decimal places only
IF RedNUMER≠1 THEN //NN
//FRACTION: iquo returns IP and decimals with no point
IPLEN:=DIGITSNEEDED(exact(RedNUMER/RedDENOM));//LEN OF IP
DP:=MID(DP,IPLEN+1); //DP
END;
//GetRepetend
REPETEND:=IFTE(RepetendLEN,MID(DP,TransientLEN+1),"");
//GetTransientDecimalPlaces
TRANSIENTPART:=IFTE(TransientLEN,LEFT(DP,TransientLEN),"");
RESULTS:=INTPART+TRANSIENTPART+USCORE+REPETEND;
//PRINT("RESULTS: "+RESULTS);
//Imp:Sometimes the string contains source
IF INSTRING(RESULTS,"M") THEN
RESULTS:=STNANImp+RESULTS;//MARK BAD RESULT
END;
//Return a formatted string
//(Relevant Lengths are returned globally in ...LEN variables)
RETURN RESULTS;
END;
EXPORT Recurring(NumerN,DenomN)
BEGIN
RETURN ReDIGITS(NumerN,DenomN);
END;
//Some call them Recurring
//Some call them Repeating
EXPORT Repeating(NumerN,DenomN)
BEGIN
RETURN ReDIGITS(NumerN,DenomN);
END;
GetTransient(NUMER,NN)
//NB Only when USCORE NOT EMPTY
//NB RECALCULATES
BEGIN
LOCAL ST:=ReDIGITS(NUMER,NN);
RETURN LEFT(ST,INSTRING(ST,USCORE));
END;
GetTransientToo(NUMER,NN,ST)
//Requires ST to supply digits an4 USCORE
BEGIN
RETURN LEFT(ST,INSTRING(ST,USCORE));
END;
EXPORT Show(NumerN,DenomN)
BEGIN
LOCAL PXP:={0};
LOCAL TRANSIENTPART;
LOCAL DST;
LOCAL RedDENOM;
LOCAL KK,LST;
RECT_P();
TEXTOUT_P(CRID,0,0,3);
//TEXTOUT_P(ALGM,0,15,1);
TEXTOUT_P("Fraction,Reduced Fraction,Real,Decimal:",0,40);
// COMMON REPRESENTATIONS
TEXTOUT_P(NumerN+"/"+DenomN,0,60);
TEXTOUT_P(CAS(NumerN/DenomN)+" Reduced Fraction",0,80);//NEEDS FIXING
TEXTOUT_P(IFTE(DenomN,(NumerN/DenomN),(DIVBY0))+" Real",0,100);
//EARLY LENGTH
//Note:This is recalculated in ReDIGITS
LST:=GetLEN(NumerN,DenomN);
TEXTOUT_P("Has "+TransientLEN+" Transient digits & "+RepetendLEN+" Repetend digits",0,180);
//Main Display
DST:=Recurring(NumerN,DenomN);
//TEXTOUT_P("Has "+TransientLEN+" Transient digits & "+RepetendLEN+" Repetend digits",0,180);//IF NOT EARLY
PXP(0):=TEXTOUT_P(DST+" Decimal",0,120);
TRANSIENTPART:=GetTransientToo(NumerN,DenomN,DST);
PXP(0):=TEXTOUT_P(TRANSIENTPART+" Transient Part",0,140);
IF INSTRING(DST,"00000000000000") THEN
//Loss Of significance suspected
TEXTOUT_P(" ! Check Possible Loss Of Significance !",0,200,3,RGB(255,0,0));
END;
IF MAX(PXP)≥320 THEN //OFFSCREEN
TEXTOUT_P("[View]",320*(4/6),220,3,RGB(255,0,0));
END;
TEXTOUT_P(IFTE(WAITS>0,"Wait...","[Esc]"),320*(5/6),220,3);
KK:=WAIT(WAITS);
IF KK==9 THEN //VIEW KEY
PRINT();
PRINT(NumerN+"/"+DenomN);
PRINT(LST);
PRINT(MID(DST,1,ImpPRINT));
IF DIM(DST)>ImpPRINT THEN
PRINT("PRINT Unable");//Or:Loop
END;
//WAIT(WAITS);
END;
RETURN DST;
END;
EXPORT ShowRange(Numer1,Numer2,Denom1,Denom2)
//Show range one by one
BEGIN
LOCAL II,JJ;
FOR II FROM Numer1 TO MAX(Numer2,Numer1) DO
FOR JJ FROM Denom1 TO MAX(Denom2,Denom1) DO
Show(II,JJ);
END;
END;
END;
EXPORT ShowReciprocals(FIRST,LAST)
BEGIN
LOCAL NN;
FOR NN FROM FIRST TO MAX(LAST,FIRST) DO
Show(1,NN);
END;
END;
EXPORT MultiplicativeOrder10(WhatsLeft)
//See http://www.hpmuseum.org/forum/thread-3212.html
// WHATSLEFT
BEGIN
LOCAL AP:=0;
LOCAL NOTFND:=1;
LOCAL LST:={};//POSSIBLES TO CHECK
IF WhatsLeft≠1 THEN
//Get possibles
LST:=mat2list(idivis(euler(WhatsLeft)));
IF TYPE(LST)≠TYPE({}) THEN //EMPIRICAL TEST
//EG NN=1ᴇ14
MSGBOX(ImpLimST+LST);
RECT_P();//SAVE/RESTORE IDEALLY
//PERHAPS RETURN AND HANDLE NAN INSTEAD
//IF NOT CAUGHT powmod WILL FAIL
END;
IF TYPE(LST)==TYPE({}) THEN
//Search possiblesfor a match
WHILE AP<SIZE(LST) AND NOTFND DO
AP:=AP+1;
IF powmod(10,LST(AP),WhatsLeft)==1 THEN
NOTFND:=0;//FOUND
END; //IF
END;//LOOP
END;//IF
//Return match
END;//VALID LST
//Return 0 if WL=1 or no match
RETURN IFTE(NOTFND,0,LST(AP));
END;
EXPORT TESTS(TESTN)
//Perform some tests
BEGIN
LOCAL III,RR;
FOR III FROM 1 TO TESTN DO
//RECT_P();
//TEXTOUT_P(III,0,120);
TESTED:=III;
RR:=ReDIGITS(2,III);
//Note the biggest
IF TransientLEN>BigT THEN
BigT:=TransientLEN;
END;
IF RepetendLEN>BigR THEN
BigR:=RepetendLEN;
END;
DRAWMENU(III,BigT,BigR);//MAX
//DRAWMENU(III,TransientLEN,RepetendLEN);//CURRENT
END;
END;
EXPORT REDECIMALS()
BEGIN
LOCAL TESTN:=1000;
ABOUT();
//HEADLINE RATE: LARGER WILL BE SLOWER
PRINT(TESTN+" fractions in "+TEVAL(TESTS(TESTN)));
//PRINT(DIM(Recurring(1,29989)));
//HELP();
END;
Note that some long repetends incorrectly show all zeros. These will be highlighted in Show but do not raise a NaN.