1

I am trying to write an algorithm that will calculate the Nth root of a number that is provided to the algorithm (practically) as a string, in C#. What I have right now calculates the square root, but not other roots, and I want to modify the part of the algorithm that is square root specific in order to generalize it to Nth roots.

I have been using two WikiHow articles (URLs in code comments, below) on calculating square and Nth roots manually, and I believe that the only part of what I have that is specific to the square root approach is isolated in my local function, (long, long) GetDigit( long segment ). I don't understand the math in the Nth root approach that differs from the square root approach, and I am doing this for programming practice and to develop my own number library that will then be generally useful, not as a math exercise. I am hoping that someone will see this question that already knows C# and the math involved.

I say that the number is provided to the algorithm "practically" as a string because I am developing my own "Number" type that stores both a numerator and a denominator as BigIntegers, so that it has the unlimited size benefit of BigIntegers and also supports decimal points.

For the purposes of this question, the algorithm might as well be receiving the number as a string, so I have modified it slightly so that it will accept and use a string if the Number-type parameter (changed to an object parameter in the code below) is null. That way you should easily be able to test any modifications you make without having to worry about the details of my Number type.

public static string NthRoot( int nth, object aNumber, string aStringInteger = "" )
{
    Debug.WriteLine( $"\nFINDING THE {nth}TH ROOT OF {aNumber}." );
    string[] Segments = GetSegments( aNumber?.Integer.ToString( ) ?? aStringInteger );
    Debug.WriteLine( $"Segment Count: {Segments.Length}." );

    string Result = "";
    long SegmentIndex = 0;
    long NextSegment = Convert.ToInt64( Segments[SegmentIndex] );
    long CurrentDigit = 0;
    long SubtractAmount;
    (CurrentDigit, SubtractAmount) = GetDigit( NextSegment );
    Result += CurrentDigit.ToString( );
    Debug.WriteLine( $"{Result}." );

    bool DoForLoop( )
    {
        for( ; SegmentIndex < Segments.Length; SegmentIndex++ )
        {
            NextSegment = (NextSegment - SubtractAmount) * (int)Math.Pow( 10, nth );
            NextSegment += Convert.ToInt64( Segments[SegmentIndex] );
            (CurrentDigit, SubtractAmount) = GetDigit( NextSegment );
            Debug.WriteLine( $"Adding {CurrentDigit} to result string." );
            Result += CurrentDigit.ToString( );
            Debug.WriteLine( $"{Result}." );
            if( Math.Pow( Result, nth ) == aNumber ) return true;
        }
        return false;
    }

    SegmentIndex++;
    if( DoForLoop( ) ) return Result;

    Result += ".";
    Segments = GetSegments( aNumber?.Decimal( ).Replace( "~", "" ) ?? "", false, 20 ); 
    //My Number type appends a ~ to repeating decimals after two repetitions of the repeating pattern.
    SegmentIndex = 0;
    DoForLoop( );
    return Result;

    string[] GetSegments( string remaining, bool padOnLeft = true, int extraPaddingOnRight = 0 )
    {
        int SegmentCount = remaining.Length / nth; //This is the number of complete nth-long sequences.
        if( remaining.Length % nth != 0 ) SegmentCount++;

        int PadLeft;
        if( padOnLeft )
        {
            PadLeft = (SegmentCount * nth) - remaining.Length;
            Debug.WriteLine( $"Padding the left with {PadLeft} zeros." );
        }
        else
        {
            PadLeft = 0;
            remaining = remaining.PadRight( SegmentCount * nth + extraPaddingOnRight, '0' );
            SegmentCount = remaining.Length / nth;
        }
        string[] _Segments = new string[SegmentCount];
        for( long s = 0; s < SegmentCount; s++ )
        {
            _Segments[s] = remaining.Substring( 0, nth - PadLeft );
            remaining = remaining.Substring( nth - PadLeft );
            PadLeft = 0;
        }
        return _Segments;
    }

    (long, long) GetDigit( long segment )
    {
        if( segment == 0 ) return (0, 0);

        long Candidate = 0;
        long Subtract = 0;
        long NextCandidate = Candidate + 1;
        if( nth != 2 ) throw new NotImplementedException( 
            "The lines that follows this exception are where I do not know how to generalize the square root " + 
            "approach to an Nth root approach." );
        //What I have is based on: method 2 at https://m.wikihow.com/Calculate-a-Square-Root-by-Hand
        //This appears to be the only part that differs from: https://www.wikihow.com/Find-Nth-Roots-by-Hand
        //The math at issue is in step 4 for Nth roots and steps 4 to 6 for square roots.
        string LeftNext = (CurrentResult( ) * 2).ToString( ) + NextCandidate.ToString( );
        long SubtractNext = Convert.ToInt64( LeftNext ) * NextCandidate;
        Debug.WriteLine( $"GetDigit( {segment} ):" );
        while( SubtractNext <= segment )
        {
            Candidate = NextCandidate;
            Subtract = SubtractNext;
            NextCandidate++;
            LeftNext = (CurrentResult( ) * 2).ToString( ) + NextCandidate.ToString( );
            SubtractNext = Convert.ToInt64( LeftNext ) * NextCandidate;
        }
        return (Candidate, Subtract);
    }

    long CurrentResult( ) => Result == "" ? 0 : Convert.ToInt64( Result.Replace( ".", "" ) );
}
RichardB
  • 114
  • 9
  • 5
    The local functions really make your method hard to read. I think those should be fellow (albeit, private) methods. – maccettura Jan 02 '20 at 06:39
  • 1
    Have you seen this question? https://stackoverflow.com/questions/18657508/c-sharp-find-nth-root/18657674 It may help you to understand nth rooth calculation in c#. – Süleyman Sümertaş Jan 02 '20 at 06:46
  • 3
    Calculate "manually" means you try to Avoid Math.Sqrt or Math.Pow ? Is this for learning purposes ? There is no root, with a definite precision, it makes no sense to use bigint, or biging/bigint, you will end up rounded numbers always ! Even if you express it as 3.4948383/938.9999 or whatever. First question on computing a root is: How precise do you want your result. And you can say 10 digits, or 15 digits, or 20 digits ... but there is no way around. Up to 15 digits you can use Math.Pow with double data type without any loss of information. – Holger Jan 02 '20 at 07:46
  • 2
    Newton's method for the nth root is explained nicely in this [Wikepedia article](https://en.wikipedia.org/wiki/Nth_root_algorithm). – Axel Kemper Jan 02 '20 at 09:56
  • S.S.: Yes I've seen that. Those calculations do not produce precise results because they involve floating point numbers. – RichardB Jan 04 '20 at 03:10
  • 1
    Holger: As I explained, I am implementing my own Number class, which uses BigIntegers. Also, the question is about Nth roots, so Math.Sqrt is not applicable. Because I am implementing my own Number class, making my own power method that supports fractional exponents is the exact same thing as what I am trying to do here. – RichardB Jan 04 '20 at 03:11
  • maccettura: the only thing that GetDigit references from outside of itself is CurrentResult. GetSegments does not reference anything from outside of itself. They are local for organizational purposes. – RichardB Jan 04 '20 at 03:22
  • Holger: I am trying to support numbers that are larger than double or decimal. – RichardB Jan 04 '20 at 03:26
  • @RichardB For what it is worth, I agree with maccettura. While its true that `GetDigit` only references `CurrentResult` from the outer scope, we still need to perform that mental analysis to determine that. Elevating the methods to the class-level would make code analysis easier for those not already familiar with it. Also, by my count your method is 95 lines, and the inner methods are 63 of that, or 2/3 of the entire thing. That seems like a lot. Elevating them would also make them unit-testable. This is just my 2c. I find this algorithm and your implementation very interesting. –  Jan 10 '20 at 01:38
  • Why not `Math.Pow(x, 1.0/nth)` ? Also can you provide an example that we can use to test code. – John Alexiou Jan 10 '20 at 01:47

0 Answers0