Repeating Decimals / Recurring Decimals

+- HP Forums (http://www.hpmuseum.org/forum)
+-- Forum: HP Software Libraries (/forum-10.html)
+--- Forum: HP Prime Software Library (/forum-15.html)
+--- Thread: Repeating Decimals / Recurring Decimals (/thread-9986.html)



Repeating Decimals / Recurring Decimals - StephenG1CMZ - 02-10-2018 05:15 PM

Given an integer reciprocal, analyzes the decimal representation to identify the transient and repeating/recurring parts of the decimal.

Eg
1/3 has no transient and 3 repeats.
1/4 has transient 0.25 and no repetition.
1/7 has no transient and 0.142857 recurring.
1/14 has transient 0 and 6 recurring digits.

See http://www.hpmuseum.org/forum/thread-9919.html
For a description of the algorithms and more examples.

(PPL) Version 0.2 follows the sequence of steps in Joe Horn's algorithm using MultiplicateOrder, which can be found here: http://www.hpmuseum.org/forum/thread-3212.html

Output is currently a list:
1: {Length of transient, transient}
2: {Length of recurring part, both parts but omitting the 0. Or 1.}
3: Just as a check, the real value.
The formatting could be improved.

Note that if the length of the recurring part exceeds 12 the recurring part returned is unreliable and should probably be replaced by NaN (the indicated length is OK, but not the digits...and sometimes they are not digits).


Version 0.3 includes an optimised MultiplicativeOrder (base 10) and outputs a string with the recurring part annotated with an underscore (selectable).

Note there is a bug with the string formatting...Sometimes it includes the integer part, sometimes not.

Version 0.4 has cosmetic improvements and help. It adds a CAS variable which show more digits, but I have yet to find out how use that in PPL - Whenever I try to turn it into a string, it turns into a real instead.
(Update: I have found the required syntax to do that now, so expect another release soon.)


Version 0.5 is a much improved PPL program for showing transient parts and repetends of reciprocals. It has been rewritten to use CAS, enabling Transients and Repetends >12 digits to be handled, and is faster.


Version 0.6
Show optimised. Note that the transient display assumes USCORE (underscore) is not empty.
Implementation Limits added (Some large values eg NN=1E14 exceed the limits of euler).


In V0.7, 1/1E14 is reduced to 1E-14/1 and non-integer numerator is processed, resulting in a NaN with no error message from MultiplicativeOrder10 seen.
Potentially, there might be other values which MultiplicateOrder10 might have handled, but which are not being processed because the reduction step is introducing unexpected non-integer values.


Version 0.8
Improved handling of implementation limitations.
Improved user interface shows more digits on-screen and allows long strings not to be generated.


Version 0.9 fixes a bug caused by "exact", introduced in V0.8. Thanks for spotting that.
Error MSGBOX is improved (using an AFile to save/restore the screen).

Code:

  
 LOCAL CRID:="REPEATING DECIMAL V 0.9 \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 RedNUMER,RedDENOM;
 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 FL:="ZTmpMESSAGEBOX";

 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("Key [Esc] to continue to next page");
  PRINT("Key [View] for more digits");
  PRINT("(Do not tap)");
  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//Lengths");
  PRINT("RedNUMER, RedDENOM//Reduced");
  PRINT("(set by GetLEN,Recurring,Repeating)");
  PRINT(" ");
  PRINT("Known Limitations:");
  PRINT("Numbers beyond about 1/12109:");
  PRINT("Repetend digits incorrectly show just 0-s (LEN OK)");
  PRINT("Negative inputs may error.");
  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/: 1ᴇ14}//Imp");
  PRINT("{1/: 3^15}//ImpCRASH AVOIDED");
  PRINT("{2/: 3}");
  PRINT("{2/: 16384}");
  PRINT("{123/208}");

 END;

 //STANDARD ROUTINES

 DecSepNow()
 //RETURN current system decimal separator
 //From the forum
 BEGIN
  RETURN IFTE((HSeparator MOD 9)<4,".",",");
 END;

 DIGITSNEEDED(NN)
 //DIGITS NEEDED FOR IP
 //EXCLUDES SIGN POINT SEPARATOR
 BEGIN
  //RETURN IP(LOG(MAX(1,ABS(NN))))+1;//PORTABLE
  RETURN MAX(0,(XPON(NN)))+1;//PRIME XPON=BASE 10
 END;

 SCR_GET()
 BEGIN
  G0:=AFiles(FL)
 END;

 SCR_PUT()
 BEGIN
  AFiles(FL):=G0;
 END;

 ZMSGBOX(ST,OKC)
 BEGIN
 
  SCR_PUT();
  OKC:=MSGBOX(ST,OKC);
  SCR_GET();
  RETURN OKC;
 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}
 //NEGATIVES:UNCHECKED
 BEGIN
  LOCAL GD; 
  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;
  //NOTE: 0,0 IS OFTEN SUGGESTIVE OF AN ERROR
  //BUT IS ALSO RETURNED BY 1/1
  //SO MAYBE USE AN ERRORFLAG?
  RETURN {TransientLEN,RepetendLEN,NDIGITS};
 END;

 ReDIGITS (NUMER,NN)
 //Determine Digits of the fraction (NUMER/NN)
 //NUMERATOR INTEGER 
 //DENOMINATOR INTEGER
 //NEGATIVES:ATTEMPTED BUT RAISE ERROR
 //DENOMINATOR 0: RETURN NAN
 //NONINTEGER: RETURN NAN
 BEGIN
  LOCAL GD;
  LOCAL LST;//UNUSED
  LOCAL ST:="";//STRINGVERSION
  LOCAL STR:="";
  LOCAL TRANSIENTPART,REPETEND,INTPART;
  LOCAL RESULTS;
  LOCAL DP;//DECIMAL PLACES
  LOCAL IPLEN;

  //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<0 OR NN<0 THEN 
   RESULTS:=ReDIGITS(ABS(NUMER),ABS(NN));
   RETURN IFTE((NUMER/NN)<0,"−"+RESULTS,RESULTS);
  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
  //DEBUG;
   //FRACTION: iquo returns IP and decimals with no point
   IPLEN:=DIGITSNEEDED((IP(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);  
  //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 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(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);
    TEXTOUT_P(RedNUMER+"/"+RedDENOM+" Reduced Fraction",0,80);
    
    //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 OKC:=0;
  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
    OKC:=ZMSGBOX((ImpLimST+LST),OKC);
    //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;

 TESTD()
 BEGIN
  LOCAL II;
  FOR II FROM 1 TO 1000 DO
   DIGITSNEEDED(123.4);
  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;

Some long repetends continue to incorrectly show all zeros.
Negative fractions may now cause a MultiplicativeOrder10 error message
Update: Some values are incorrect...