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); }
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); }
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); }
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); }
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(); } }
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); }