Ejemplo n.º 1
0
        public BigInt ShortMultiply(ushort c)
        {
            // optimized special case for multiplying a BigInt by a ushort
            // (normal operator* will work OK but this is a bit faster)

            //Work out roughly how many digits the result will contain and create a BigInt with sufficient capacity.
            int    numCDigits = Math.Abs(c).ToString().Length;
            BigInt result     = new BigInt(StorageSizeForDigits(NumDigits + numCDigits), 0);

            BigInt currChunkResult = 0;
            int    carry = 0, currChunkProduct;

            for (int chunkIndex = 0; chunkIndex < NumChunks; chunkIndex++)
            {
                currChunkProduct = c * GetChunkAbsolute(chunkIndex) + carry;
                carry            = currChunkProduct / 1000;
                result.AppendChunk((ushort)(currChunkProduct - (carry * 1000)));
            }
            if (carry > 0)
            {
                result.AppendChunk((ushort)carry);
            }
            result._sign = this.Sign;
            result.AfterUpdate();
            return(result);
        }
Ejemplo n.º 2
0
        private static BigInt DivideAndConquerMultiply(BigInt a, BigInt b)
        {
            //faster divide-and-conquer multiplication, called by operator* when a and b are
            //large.  Do not call directly.

            int numChunks = MaxNumChunks(a, b);

            if (MinNumChunks(a, b) < D_AND_C_THRESHOLD)
            {
                return(a * b);
            }

            if ((numChunks & 1) == 1)  // if numChunks is odd, add 1
            {
                numChunks++;
            }

            int N       = numChunks / 2;
            int Ndigits = N + N + N; // N chunks = 3N digits
            //set a0,a1,b0,b1 such that a = (1000^N)*A1 + A0, b = (1000^N)*B1 + B0
            //then a*b = (1000^(2N) + 1000^N)A1B1 + 1000^N(A1-A0)(B0-B1) + (1000^N + 1)A0B0

            int    capacity = StorageSizeForDigits(a.NumDigits + b.NumDigits);
            BigInt a0       = new BigInt(capacity, 0);
            BigInt a1       = new BigInt(capacity, 0);
            BigInt b0       = new BigInt(capacity, 0);
            BigInt b1       = new BigInt(capacity, 0);

            for (int i = 0; i < N; i++)
            {
                a0.AppendChunk((ushort)a.GetChunkAbsolute(i));
                a1.AppendChunk((ushort)a.GetChunkAbsolute(i + N));
                b0.AppendChunk((ushort)b.GetChunkAbsolute(i));
                b1.AppendChunk((ushort)b.GetChunkAbsolute(i + N));
            }

            a0.AfterUpdate(); //TODO: get rid of these? and call result.AfterUpdate?
            a1.AfterUpdate();
            b0.AfterUpdate();
            b1.AfterUpdate();

            BigInt part1 = DivideAndConquerMultiply(a1, b1);
            BigInt part2 = DivideAndConquerMultiply(a1 - a0, b0 - b1);
            BigInt part3 = DivideAndConquerMultiply(a0, b0);

            BigInt result = (part1 << 2 * Ndigits) + (part1 << Ndigits) + (part2 << Ndigits) + (part3 << Ndigits) + part3;

            result._sign = (short)(a.Sign * b.Sign); //-ve * -ve == +ve, etc
            return(result);
        }
Ejemplo n.º 3
0
        public static BigInt operator <<(BigInt a, int n)
        {
            if (n < 0)
            {
                throw new ArgumentOutOfRangeException("n", "Shift amount must not be negative.");
            }

            //return a BigInt which is a padded on the right with n zeros
            a.EnsureInitialized();
            //Create a BigInt that should have enough capacity for the result (to save having to do slow array resizes).
            BigInt result = new BigInt(StorageSizeForDigits(a.NumChunks + n), 0);
            int    i, shiftRemaining, mask, shifter, currChunk, carry = 0;

            for (shiftRemaining = n; shiftRemaining > 2; shiftRemaining -= 3)  // add chunks of 3 zeros until 2 digits or less remaining
            {
                result.AppendChunk(0);
            }

            switch (shiftRemaining)
            {
            case 0:     // n is a multiple of three, so all that's left is to copy the digits of a
                for (i = 0; i < a.NumChunks; i++)
                {
                    result.AppendChunk((ushort)(a.GetChunkAbsolute(i)));
                }
                break;

            case 1:     // n not multiple of 3 so need some intra-chunk shifting
            case 2:
                if (shiftRemaining == 1)
                {
                    mask = 100; shifter = 10;
                }
                else
                {
                    mask = 10; shifter = 100;
                }
                for (i = 0; i < a.NumChunks; i++)
                {
                    currChunk = a.GetChunkAbsolute(i);
                    result.AppendChunk((ushort)((currChunk % mask) * shifter + carry));
                    carry = currChunk / mask;
                }
                break;
            }
            result._sign = a.Sign;
            result.AfterUpdate();
            return(result);
        }
Ejemplo n.º 4
0
        public static BigInt operator *(BigInt a, BigInt b)
        {
            a.EnsureInitialized();
            b.EnsureInitialized();

            if (MinNumChunks(a, b) >= D_AND_C_THRESHOLD)
            {
                // for larger numbers use faster divide-and-conquer routine
                return(BigInt.DivideAndConquerMultiply(a, b));
            }

            //perform multiplication using standard O(N^2) algorithm
            BigInt result = new BigInt(StorageSizeForDigits(a.NumDigits + b.NumDigits), 0);
            BigInt currChunkResult = new BigInt(result.Capacity + 8, 0);
            short  resultSign = (short)(a.Sign * b.Sign); //the sign that the result will have
            int    carry, aChunks = a.NumChunks, bChunks = b.NumChunks;

            for (int aChunkIndex = 0; aChunkIndex < aChunks; aChunkIndex++)
            {
                carry = 0;
                short aChunk = a.GetChunkAbsolute(aChunkIndex);
                if (aChunk > 0)
                {
                    int currChunkProduct = 0;
                    for (int bChunkIndex = 0; bChunkIndex < bChunks; bChunkIndex++)
                    {
                        currChunkProduct = aChunk * b.GetChunkAbsolute(bChunkIndex) + carry;
                        carry            = currChunkProduct / 1000;
                        currChunkResult.AppendChunk((ushort)(currChunkProduct - (carry * 1000)));
                    }
                    if (carry > 0)
                    {
                        currChunkResult.AppendChunk((ushort)carry);
                    }
                    result += currChunkResult;
                }
                // set currChunkResult back to just its RHS zero padding...
                currChunkResult._numChunks = aChunkIndex;
                // ...and add another chunk of zero padding for the next round
                currChunkResult.AppendChunk(0);
            }
            result._sign = resultSign;
            result.AfterUpdate();
            return(result);
        }
Ejemplo n.º 5
0
        public static BigInt operator +(BigInt a, BigInt b)
        {
            a.EnsureInitialized();
            b.EnsureInitialized();

            bool   isSubtraction = false;
            BigInt result        = new BigInt(Math.Max(a.Capacity, b.Capacity), 0);

            if (a.Sign != b.Sign)
            {
                bool doSignSwapAddition = false;
                if (a.Sign == -1)
                {
                    doSignSwapAddition = (BigInt.CompareMagnitudes(a, b) == 1);
                }
                else //b.Sign == -1
                {
                    doSignSwapAddition = (BigInt.CompareMagnitudes(a, b) == -1);
                }
                if (doSignSwapAddition)
                {       // our standard addition algorithm doesn't work when a < 0 and b > 0 and |a| > |b| (or vice versa)
                    // becauase we don't look ahead to see if we can borrow, so instead calculate -((-a) + (-b))
                    a._sign = (short)-a.Sign;
                    b._sign = (short)-b.Sign;
                    result  = -(a + b);
                    a._sign = (short)-a.Sign;   // put signs back
                    b._sign = (short)-b.Sign;   // the way they were
                    return(result);
                }
                else
                {
                    isSubtraction = true;
                }
            }

            int currChunkSum = 0, carry = 0, currChunkToAppend;
            int numChunksToAdd = MaxNumChunks(a, b);

            for (int i = 0; i < numChunksToAdd; i++)
            {
                short borrow = 0, aChunk = a.GetChunk(i), bChunk = b.GetChunk(i);
                if (isSubtraction && ((a.Sign == 1 && (aChunk + carry < Math.Abs(bChunk))) ||
                                      (b.Sign == 1 && (bChunk + carry < Math.Abs(aChunk)))))
                {
                    //we're adding +ve and -ve, and the chunk in the positive number (combined with carry)
                    // is smaller than the chunk in the negative number, so have to borrow
                    borrow = 1000;
                }
                currChunkSum = aChunk + bChunk + borrow + carry;
                if (borrow > 0)
                {
                    carry             = -1;
                    currChunkToAppend = currChunkSum;
                }
                else if (currChunkSum >= 1000)
                {
                    carry             = 1;
                    currChunkToAppend = currChunkSum - 1000;
                }
                else if (currChunkSum <= -1000)
                {
                    carry             = -1;
                    currChunkToAppend = (-currChunkSum) - 1000;
                }
                else
                {
                    carry             = 0;
                    currChunkToAppend = Math.Abs(currChunkSum);
                }
                result.AppendChunk((ushort)currChunkToAppend);
            }
            if (carry != 0)
            {
                result.AppendChunk((ushort)Math.Abs(carry));
            }

            if (a._sign == b._sign)
            {
                result._sign = a._sign;
            }
            else
            {
                result._sign = (currChunkSum < 0) ? (short)-1 : (short)1;
            }

            result.AfterUpdate();
            return(result);
        }
Ejemplo n.º 6
0
        private static void DivAndMod(DivModOperationType operationType, BigInt a, BigInt b, out BigInt remainder, out BigInt result)
        // sets <result> to the result of dividing a by b, and sets <remainder> to the remainder when
        // a is divided by b
        {
            if (b.IsZero())
            {
                throw new DivideByZeroException();
            }

            if (CompareMagnitudes(a, b) < 0)  // if a is less than b then it's a no-brainer
            {
                if (operationType != DivModOperationType.DivideOnly)
                {
                    remainder = a;
                }
                else
                {
                    remainder = 0;
                }
                result = 0;
                return;
            }

            if (a.NumChunks <= 3)
            {   //a, b small - just use machine arithmetic
                remainder = 0;
                int intA = Convert.ToInt32(a.ToString());
                int intB = Convert.ToInt32(b.ToString());
                if (operationType != DivModOperationType.DivideOnly)
                {
                    remainder = intA % intB;
                }
                if (operationType != DivModOperationType.ModulusOnly)
                {
                    result = intA / intB;
                }
                else
                {
                    result = 0;
                }
                return;
            }

            result = new BigInt(a.Capacity, 0);

            BigInt removal, resultStore = new BigInt(a.Capacity, 0);

            short  resultSign = (short)(a.Sign * b.Sign);
            short  bSign      = b.Sign;
            ushort trialDiv;
            short  comparison;
            int    numBdigits = b.NumDigits;

            remainder       = a;
            remainder._sign = 1;
            b._sign         = 1;                                // work with positive numbers

            int bTrial = 0;

            if (b.NumChunks == 1)
            {
                bTrial = b.GetChunkAbsolute(b.NumChunks - 1);
            }
            else
            {
                bTrial = b.GetChunkAbsolute(b.NumChunks - 1) * 1000 + b.GetChunkAbsolute(b.NumChunks - 2);
            }

            int bTrialDigits = (int)Math.Floor(Math.Log10(bTrial)) + 1;
            int thisShift = 0, lastShift = -1;


            while (remainder >= b)
            {
                int   lastRemainderDigits = remainder.NumDigits;
                int   remTrial;
                short remMostSigChunk = remainder.GetChunkAbsolute(remainder._numChunks - 1);
                //remTrial is formed by taking the most significant 1, 2 or 3 chunks of remainder;
                // take the minimum number of chunks needed to make remTrial bigger than bTrial.
                if (remMostSigChunk >= bTrial)
                {
                    remTrial = remMostSigChunk;
                }
                else
                {
                    remTrial = remMostSigChunk * 1000 + remainder.GetChunkAbsolute(remainder.NumChunks - 2);
                    if (remTrial < bTrial)
                    {
                        remTrial = (remTrial * 1000) + remainder.GetChunkAbsolute(remainder.NumChunks - 3);
                    }
                    //now remTrial should be >= bTrial.  remTrial == bTrial is the nasty special case that produces trialDiv == 1.
                }

                trialDiv = (ushort)(remTrial / bTrial); // this is our guess of the next quotient chunk
                removal  = b.ShortMultiply(trialDiv);
                int remTrialDigits = (int)Math.Floor(Math.Log10(remTrial)) + 1;
                thisShift = remainder.NumDigits - (remTrialDigits - bTrialDigits) - numBdigits;
                removal   = removal << thisShift;

                // normally, the required shift drops by three (the number of digits in a chunk) with
                // each loop iteration.  But if the result contains a 000 chunk in the middle, we
                // will notice a drop in shift of more than 3.
                if (operationType != DivModOperationType.ModulusOnly)
                {
                    for (int i = 0; i < lastShift - thisShift - 3; i += 3)
                    {
                        resultStore.AppendChunk(0);
                    }
                }

                // our guess of the next quotient chunk might be too high (but not by more than two);
                // adjust it down if necessary
                BigInt bShifted    = 0;
                bool   bShiftedSet = false;
                do
                {
                    comparison = Compare(removal, remainder);
                    if (comparison > 0)
                    {
                        trialDiv--;
                        if (trialDiv == 0)
                        {
                            //This is the nasty special case.  I think that the first three cases can
                            // never happen.  Effectively what happens is that have guessed 1000 (through
                            // trialDiv = 1 and thisShift >= 3) and found that it is one high so we
                            // reduce it to 999 (through trialDiv = 999 and thisShift -= 3).
                            switch (thisShift)
                            {
                            case 0:     //This should never happen
                                break;

                            case 1:
                                trialDiv  = 9;
                                thisShift = 0;
                                break;

                            case 2:
                                trialDiv  = 99;
                                thisShift = 0;
                                break;

                            default:
                                trialDiv   = 999;
                                thisShift -= 3;
                                break;
                            }
                        }
                        if (!bShiftedSet)
                        {
                            bShiftedSet = true;
                            bShifted    = b << thisShift;
                        }
                        removal -= bShifted;
                    }
                } while (comparison > 0);

                lastShift = thisShift;

                remainder -= removal;
                if (operationType != DivModOperationType.ModulusOnly)
                {
                    resultStore.AppendChunk(trialDiv);
                }
            }

            b._sign = bSign; // put back b's sign

            if (operationType != DivModOperationType.DivideOnly)
            {
                remainder._sign = a.Sign; // sign of mod = sign of a
                remainder.AfterUpdate();
            }

            if (operationType != DivModOperationType.ModulusOnly)
            {
                for (int i = 3; i <= thisShift; i += 3)
                {
                    resultStore.AppendChunk(0);
                }
                //resultStore now holds the quotient chunks in reverse order.  Flip them and return.
                for (int i = resultStore.NumChunks - 1; i >= 0; i--)
                {
                    result.AppendChunk((ushort)resultStore.GetChunkAbsolute(i));
                }
                result._sign = resultSign;
                result.AfterUpdate();
            }
        }