Getting a specific digit from a ratio expansion in any base (nth digit of x/y)

后端 未结 5 638
一向
一向 2020-12-09 06:23

Is there an algorithm that can calculate the digits of a repeating-decimal ratio without starting at the beginning?

I\'m looking for a solution that doesn\'t use ar

5条回答
  •  無奈伤痛
    2020-12-09 07:17

    AHA! caffiend: your comment to my other (longer) answer (specifically "duplicate remainders") leads me to a very simple solution that is O(n) where n = the sum of the lengths of the nonrepeating + repeating parts, and requires only integer math with numbers between 0 and 10*y where y is the denominator.

    Here's a Javascript function to get the nth digit to the right of the decimal point for the rational number x/y:

    function digit(x,y,n) 
    { 
       if (n == 0) 
          return Math.floor(x/y)%10; 
       return digit(10*(x%y),y,n-1);
    }
    

    It's recursive rather than iterative, and is not smart enough to detect cycles (the 10000th digit of 1/3 is obviously 3, but this keeps on going until it reaches the 10000th iteration), but it works at least until the stack runs out of memory.

    Basically this works because of two facts:

    • the nth digit of x/y is the (n-1)th digit of 10x/y (example: the 6th digit of 1/7 is the 5th digit of 10/7 is the 4th digit of 100/7 etc.)
    • the nth digit of x/y is the nth digit of (x%y)/y (example: the 5th digit of 10/7 is also the 5th digit of 3/7)

    We can tweak this to be an iterative routine and combine it with Floyd's cycle-finding algorithm (which I learned as the "rho" method from a Martin Gardner column) to get something that shortcuts this approach.

    Here's a javascript function that computes a solution with this approach:

    function digit(x,y,n,returnstruct)
    {
      function kernel(x,y) { return 10*(x%y); }
    
      var period = 0;
      var x1 = x;
      var x2 = x;
      var i = 0;
      while (n > 0)
      {
        n--;
        i++;
        x1 = kernel(x1,y); // iterate once
        x2 = kernel(x2,y);
        x2 = kernel(x2,y); // iterate twice  
    
        // have both 1x and 2x iterations reached the same state?
        if (x1 == x2)
        {
          period = i;
          n = n % period;
          i = 0; 
          // start again in case the nonrepeating part gave us a
          // multiple of the period rather than the period itself
        }
      }
      var answer=Math.floor(x1/y);
      if (returnstruct)
        return {period: period, digit: answer, 
          toString: function() 
          { 
            return 'period='+this.period+',digit='+this.digit;
          }};
      else
        return answer;
    }
    

    And an example of running the nth digit of 1/700:

    js>1/700
    0.0014285714285714286
    js>n=10000000
    10000000
    js>rs=digit(1,700,n,true)
    period=6,digit=4
    js>n%6
    4
    js>rs=digit(1,700,4,true)
    period=0,digit=4
    

    Same thing for 33/59:

    js>33/59
    0.559322033898305
    js>rs=digit(33,59,3,true)
    period=0,digit=9
    js>rs=digit(33,59,61,true)
    period=58,digit=9
    js>rs=digit(33,59,61+58,true)
    period=58,digit=9
    

    And 122222/990000 (long nonrepeating part):

    js>122222/990000
    0.12345656565656565
    js>digit(122222,990000,5,true)
    period=0,digit=5
    js>digit(122222,990000,7,true)
    period=6,digit=5
    js>digit(122222,990000,9,true)
    period=2,digit=5
    js>digit(122222,990000,9999,true)
    period=2,digit=5
    js>digit(122222,990000,10000,true)
    period=2,digit=6
    

    Here's another function that finds a stretch of digits:

    // find digits n1 through n2 of x/y
    function digits(x,y,n1,n2,returnstruct)
    {
      function kernel(x,y) { return 10*(x%y); }
    
      var period = 0;
      var x1 = x;
      var x2 = x;
      var i = 0;
      var answer='';
      while (n2 >= 0)
      {
        // time to print out digits?
        if (n1 <= 0) 
          answer = answer + Math.floor(x1/y);
    
        n1--,n2--;
        i++;
        x1 = kernel(x1,y); // iterate once
        x2 = kernel(x2,y);
        x2 = kernel(x2,y); // iterate twice  
    
        // have both 1x and 2x iterations reached the same state?
        if (x1 == x2)
        {
          period = i;
          if (n1 > period)
          {
            var jumpahead = n1 - (n1 % period);
            n1 -= jumpahead, n2 -= jumpahead;
          }
          i = 0; 
          // start again in case the nonrepeating part gave us a
          // multiple of the period rather than the period itself
        }    
      }
      if (returnstruct)
        return {period: period, digits: answer, 
          toString: function() 
          { 
            return 'period='+this.period+',digits='+this.digits;
          }};
      else
        return answer;
    }
    

    I've included the results for your answer (assuming that Javascript #'s didn't overflow):

    js>digit(1,7,1,7,true)
    period=6,digits=1428571
    js>digit(1,7,601,607,true)
    period=6,digits=1428571
    js>1/7
    0.14285714285714285
    js>digit(2124679,214748367,214748300,214748400,true)
    period=1759780,digits=20513882650385881630475914166090026658968726872786883636698387559799232373208220950057329190307649696
    js>digit(122222,990000,100,110,true)
    period=2,digits=65656565656
    

提交回复
热议问题