public static BigInteger ModPow(BigInteger value, BigInteger exponent, BigInteger modulus)
        {
            if (exponent.Sign < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(exponent));
            }

            value.AssertValid();
            exponent.AssertValid();
            modulus.AssertValid();

            bool trivialValue    = value._bits == null;
            bool trivialExponent = exponent._bits == null;
            bool trivialModulus  = modulus._bits == null;

            if (trivialModulus)
            {
                uint bits = trivialValue && trivialExponent?BigIntegerCalculator.Pow(NumericsHelpers.Abs(value._sign), NumericsHelpers.Abs(exponent._sign), NumericsHelpers.Abs(modulus._sign)) :
                                trivialValue?BigIntegerCalculator.Pow(NumericsHelpers.Abs(value._sign), exponent._bits, NumericsHelpers.Abs(modulus._sign)) :
                                    trivialExponent?BigIntegerCalculator.Pow(value._bits, NumericsHelpers.Abs(exponent._sign), NumericsHelpers.Abs(modulus._sign)) :
                                        BigIntegerCalculator.Pow(value._bits, exponent._bits, NumericsHelpers.Abs(modulus._sign));

                return(value._sign < 0 && !exponent.IsEven ? -1 * bits : bits);
            }
            else
            {
                uint[] bits = trivialValue && trivialExponent?BigIntegerCalculator.Pow(NumericsHelpers.Abs(value._sign), NumericsHelpers.Abs(exponent._sign), modulus._bits) :
                                  trivialValue?BigIntegerCalculator.Pow(NumericsHelpers.Abs(value._sign), exponent._bits, modulus._bits) :
                                      trivialExponent?BigIntegerCalculator.Pow(value._bits, NumericsHelpers.Abs(exponent._sign), modulus._bits) :
                                          BigIntegerCalculator.Pow(value._bits, exponent._bits, modulus._bits);

                return(new BigInteger(bits, value._sign < 0 && !exponent.IsEven));
            }
        }
예제 #2
0
        private static bool NumberToBigInteger(ref BigNumberBuffer number, out BigInteger result)
        {
            int currentBufferSize = 0;

            int totalDigitCount = 0;
            int numberScale     = number.scale;

            const int  MaxPartialDigits = 9;
            const uint TenPowMaxPartial = 1000000000;

            int[]? arrayFromPoolForResultBuffer = null;
            if (numberScale < 0)
            {
                result = default;
                return(false);
            }

            try
            {
                if (number.digits.Length <= s_naiveThreshold)
                {
                    return(Naive(ref number, out result));
                }
                else
                {
                    return(DivideAndConquer(ref number, out result));
                }
            }
            finally
            {
                if (arrayFromPoolForResultBuffer != null)
                {
                    ArrayPool <int> .Shared.Return(arrayFromPoolForResultBuffer);
                }
            }

            bool Naive(ref BigNumberBuffer number, out BigInteger result)
            {
                Span <uint> stackBuffer       = stackalloc uint[BigIntegerCalculator.StackAllocThreshold];
                Span <uint> currentBuffer     = stackBuffer;
                uint        partialValue      = 0;
                int         partialDigitCount = 0;

                foreach (ReadOnlyMemory <char> digitsChunk in number.digits.GetChunks())
                {
                    if (!ProcessChunk(digitsChunk.Span, ref currentBuffer))
                    {
                        result = default;
                        return(false);
                    }
                }

                if (partialDigitCount > 0)
                {
                    MultiplyAdd(ref currentBuffer, s_uint32PowersOfTen[partialDigitCount], partialValue);
                }

                result = NumberBufferToBigInteger(currentBuffer, number.sign);
                return(true);

                bool ProcessChunk(ReadOnlySpan <char> chunkDigits, ref Span <uint> currentBuffer)
                {
                    int remainingIntDigitCount        = Math.Max(numberScale - totalDigitCount, 0);
                    ReadOnlySpan <char> intDigitsSpan = chunkDigits.Slice(0, Math.Min(remainingIntDigitCount, chunkDigits.Length));

                    bool endReached = false;

                    // Storing these captured variables in locals for faster access in the loop.
                    uint _partialValue      = partialValue;
                    int  _partialDigitCount = partialDigitCount;
                    int  _totalDigitCount   = totalDigitCount;

                    for (int i = 0; i < intDigitsSpan.Length; i++)
                    {
                        char digitChar = chunkDigits[i];
                        if (digitChar == '\0')
                        {
                            endReached = true;
                            break;
                        }

                        _partialValue = _partialValue * 10 + (uint)(digitChar - '0');
                        _partialDigitCount++;
                        _totalDigitCount++;

                        // Update the buffer when enough partial digits have been accumulated.
                        if (_partialDigitCount == MaxPartialDigits)
                        {
                            MultiplyAdd(ref currentBuffer, TenPowMaxPartial, _partialValue);
                            _partialValue      = 0;
                            _partialDigitCount = 0;
                        }
                    }

                    // Check for nonzero digits after the decimal point.
                    if (!endReached)
                    {
                        ReadOnlySpan <char> fracDigitsSpan = chunkDigits.Slice(intDigitsSpan.Length);
                        for (int i = 0; i < fracDigitsSpan.Length; i++)
                        {
                            char digitChar = fracDigitsSpan[i];
                            if (digitChar == '\0')
                            {
                                break;
                            }
                            if (digitChar != '0')
                            {
                                return(false);
                            }
                        }
                    }

                    partialValue      = _partialValue;
                    partialDigitCount = _partialDigitCount;
                    totalDigitCount   = _totalDigitCount;

                    return(true);
                }
            }

            bool DivideAndConquer(ref BigNumberBuffer number, out BigInteger result)
            {
                Span <uint> currentBuffer;

                int[]? arrayFromPoolForMultiplier = null;
                try
                {
                    totalDigitCount = Math.Min(number.digits.Length - 1, numberScale);
                    int bufferSize = (totalDigitCount + MaxPartialDigits - 1) / MaxPartialDigits;

                    Span <uint> buffer = new uint[bufferSize];
                    arrayFromPoolForResultBuffer = ArrayPool <int> .Shared.Rent(bufferSize);

                    Span <uint> newBuffer = MemoryMarshal.Cast <int, uint>(arrayFromPoolForResultBuffer).Slice(0, bufferSize);
                    newBuffer.Clear();

                    // Separate every MaxPartialDigits digits and store them in the buffer.
                    // Buffers are treated as little-endian. That means, the array { 234567890, 1 }
                    // represents the number 1234567890.
                    int  bufferIndex            = bufferSize - 1;
                    uint currentBlock           = 0;
                    int  shiftUntil             = (totalDigitCount - 1) % MaxPartialDigits;
                    int  remainingIntDigitCount = totalDigitCount;
                    foreach (ReadOnlyMemory <char> digitsChunk in number.digits.GetChunks())
                    {
                        ReadOnlySpan <char> digitsChunkSpan = digitsChunk.Span;
                        ReadOnlySpan <char> intDigitsSpan   = digitsChunkSpan.Slice(0, Math.Min(remainingIntDigitCount, digitsChunkSpan.Length));

                        for (int i = 0; i < intDigitsSpan.Length; i++)
                        {
                            char digitChar = intDigitsSpan[i];
                            Debug.Assert(char.IsDigit(digitChar));
                            currentBlock *= 10;
                            currentBlock += unchecked ((uint)(digitChar - '0'));
                            if (shiftUntil == 0)
                            {
                                buffer[bufferIndex] = currentBlock;
                                currentBlock        = 0;
                                bufferIndex--;
                                shiftUntil = MaxPartialDigits;
                            }
                            shiftUntil--;
                        }
                        remainingIntDigitCount -= intDigitsSpan.Length;
                        Debug.Assert(0 <= remainingIntDigitCount);

                        ReadOnlySpan <char> fracDigitsSpan = digitsChunkSpan.Slice(intDigitsSpan.Length);
                        for (int i = 0; i < fracDigitsSpan.Length; i++)
                        {
                            char digitChar = fracDigitsSpan[i];
                            if (digitChar == '\0')
                            {
                                break;
                            }
                            if (digitChar != '0')
                            {
                                result = default;
                                return(false);
                            }
                        }
                    }
                    Debug.Assert(currentBlock == 0);
                    Debug.Assert(bufferIndex == -1);

                    int blockSize = 1;
                    arrayFromPoolForMultiplier = ArrayPool <int> .Shared.Rent(blockSize);

                    Span <uint> multiplier = MemoryMarshal.Cast <int, uint>(arrayFromPoolForMultiplier).Slice(0, blockSize);
                    multiplier[0] = TenPowMaxPartial;

                    // This loop is executed ceil(log_2(bufferSize)) times.
                    while (true)
                    {
                        // merge each block pairs.
                        // When buffer represents:
                        // |     A     |     B     |     C     |     D     |
                        // Make newBuffer like:
                        // |  A + B * multiplier   |  C + D * multiplier   |
                        for (int i = 0; i < bufferSize; i += blockSize * 2)
                        {
                            Span <uint> curBuffer    = buffer.Slice(i);
                            Span <uint> curNewBuffer = newBuffer.Slice(i);

                            int len      = Math.Min(bufferSize - i, blockSize * 2);
                            int lowerLen = Math.Min(len, blockSize);
                            int upperLen = len - lowerLen;
                            if (upperLen != 0)
                            {
                                Debug.Assert(blockSize == lowerLen);
                                Debug.Assert(blockSize == multiplier.Length);
                                Debug.Assert(multiplier.Length == lowerLen);
                                BigIntegerCalculator.Multiply(multiplier, curBuffer.Slice(blockSize, upperLen), curNewBuffer.Slice(0, len));
                            }

                            long carry = 0;
                            int  j     = 0;
                            for (; j < lowerLen; j++)
                            {
                                long digit = (curBuffer[j] + carry) + curNewBuffer[j];
                                curNewBuffer[j] = unchecked ((uint)digit);
                                carry           = digit >> 32;
                            }
                            if (carry != 0)
                            {
                                while (true)
                                {
                                    curNewBuffer[j]++;
                                    if (curNewBuffer[j] != 0)
                                    {
                                        break;
                                    }
                                    j++;
                                }
                            }
                        }

                        Span <uint> tmp = buffer;
                        buffer     = newBuffer;
                        newBuffer  = tmp;
                        blockSize *= 2;

                        if (bufferSize <= blockSize)
                        {
                            break;
                        }
                        newBuffer.Clear();
                        int[]? arrayToReturn = arrayFromPoolForMultiplier;

                        arrayFromPoolForMultiplier = ArrayPool <int> .Shared.Rent(blockSize);

                        Span <uint> newMultiplier = MemoryMarshal.Cast <int, uint>(arrayFromPoolForMultiplier).Slice(0, blockSize);
                        newMultiplier.Clear();
                        BigIntegerCalculator.Square(multiplier, newMultiplier);
                        multiplier = newMultiplier;
                        if (arrayToReturn is not null)
                        {
                            ArrayPool <int> .Shared.Return(arrayToReturn);
                        }
                    }

                    // shrink buffer to the currently used portion.
                    // First, calculate the rough size of the buffer from the ratio that the number
                    // of digits follows. Then, shrink the size until there is no more space left.
                    // The Ratio is calculated as: log_{2^32}(10^9)
                    const double digitRatio = 0.934292276687070661;
                    currentBufferSize = Math.Min((int)(bufferSize * digitRatio) + 1, bufferSize);
                    Debug.Assert(buffer.Length == currentBufferSize || buffer[currentBufferSize] == 0);
                    while (0 < currentBufferSize && buffer[currentBufferSize - 1] == 0)
                    {
                        currentBufferSize--;
                    }
                    currentBuffer = buffer.Slice(0, currentBufferSize);
                    result        = NumberBufferToBigInteger(currentBuffer, number.sign);
                }
                finally
                {
                    if (arrayFromPoolForMultiplier != null)
                    {
                        ArrayPool <int> .Shared.Return(arrayFromPoolForMultiplier);
                    }
                }
                return(true);
            }

            BigInteger NumberBufferToBigInteger(Span <uint> currentBuffer, bool signa)
            {
                int trailingZeroCount = numberScale - totalDigitCount;

                while (trailingZeroCount >= MaxPartialDigits)
                {
                    MultiplyAdd(ref currentBuffer, TenPowMaxPartial, 0);
                    trailingZeroCount -= MaxPartialDigits;
                }

                if (trailingZeroCount > 0)
                {
                    MultiplyAdd(ref currentBuffer, s_uint32PowersOfTen[trailingZeroCount], 0);
                }

                int sign;

                uint[]? bits;

                if (currentBufferSize == 0)
                {
                    sign = 0;
                    bits = null;
                }
                else if (currentBufferSize == 1 && currentBuffer[0] <= int.MaxValue)
                {
                    sign = (int)(signa ? -currentBuffer[0] : currentBuffer[0]);
                    bits = null;
                }
                else
                {
                    sign = signa ? -1 : 1;
                    bits = currentBuffer.Slice(0, currentBufferSize).ToArray();
                }

                return(new BigInteger(sign, bits));
            }

            // This function should only be used for result buffer.
            void MultiplyAdd(ref Span <uint> currentBuffer, uint multiplier, uint addValue)
            {
                Span <uint> curBits = currentBuffer.Slice(0, currentBufferSize);
                uint        carry   = addValue;

                for (int i = 0; i < curBits.Length; i++)
                {
                    ulong p = (ulong)multiplier * curBits[i] + carry;
                    curBits[i] = (uint)p;
                    carry      = (uint)(p >> 32);
                }

                if (carry == 0)
                {
                    return;
                }

                if (currentBufferSize == currentBuffer.Length)
                {
                    int[]? arrayToReturn = arrayFromPoolForResultBuffer;

                    arrayFromPoolForResultBuffer = ArrayPool <int> .Shared.Rent(checked (currentBufferSize * 2));

                    Span <uint> newBuffer = MemoryMarshal.Cast <int, uint>(arrayFromPoolForResultBuffer);
                    currentBuffer.CopyTo(newBuffer);
                    currentBuffer = newBuffer;

                    if (arrayToReturn != null)
                    {
                        ArrayPool <int> .Shared.Return(arrayToReturn);
                    }
                }

                currentBuffer[currentBufferSize] = carry;
                currentBufferSize++;
            }
        }