//////////////////////////////////////////////////////////////////// // BigInt.cs // Copyright © 2005, 2009 Carl Johansen // // Class for multiprecision (signed) integers. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published // by the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more // details (). //////////////////////////////////////////////////////////////////// // History: // 24-5-08 BUGFIX: DivAndMod <= test changed to < // 25-5-08 BUFGIX: Previous fix was wrong! // 25-5-08 Changed BigInt from class to struct // 16-1-09 Automatically expands internal array when full // 19-1-09 Memory management: fixed some bugs and made some improvements // 07-11-09 Added Power method, ^ operator and IsEven property. // 07-11-09 BREAKING CHANGE: modpower method renamed to ModPower for consistency. // 01-12-09 BUGFIX: Was allocating unnecessary array space. using System; using System.Text.RegularExpressions; namespace CarlJohansen { public enum DivModOperationType { DivideOnly = 0, ModulusOnly, DivAndMod } /// /// Summary description for BigNum. /// public struct BigInt { #region Private Data private const int D_AND_C_THRESHOLD = 33; private const int ARRAY_ALLOC_SIZE = 500; private ushort[] _bitblock; private int _numChunks, _numDigits; private short _sign; private int _bitblockArraySize; private int _initialCapacity; #endregion #region Constructors public BigInt(int initialValue) : this(0, initialValue) { } private BigInt(int capacity, int initialValue) : this(capacity, false) { InitBigIntFromString(initialValue.ToString()); } public BigInt(Int64 initialValue) : this(0, initialValue) { } private BigInt(int capacity, Int64 initialValue) : this(capacity, false) { InitBigIntFromString(initialValue.ToString()); } public BigInt(string initialValue) : this(0, initialValue) { } private BigInt(int capacity, string initialValue) : this(capacity, false) { InitBigIntFromString(initialValue); } private BigInt(int initialCapacity, bool notUsed) : this() { _initialCapacity = initialCapacity; if (initialCapacity > 0) { //Caller has specified a capacity (only privileged methods do this). _bitblockArraySize = initialCapacity; _bitblock = new ushort[_bitblockArraySize]; } } #endregion #region Public Properties /// /// Gets the size of the internal data structure used to store the BigInt. /// public int Capacity { get { return (_bitblock == null ? 0 : _bitblock.Length); } } public short Sign { get { return _sign; } } public int NumDigits { get { return _numDigits; } } public int NumChunks { get { return _numChunks; } } public bool IsEven { get { if (IsZero()) return true; return (GetChunk(0) & 1) == 0; } } #endregion #region Public Methods /// /// Compares two BigInts /// /// 1 if a > b, 0 if a == b, -1 if a is less than b public static short Compare(BigInt a, BigInt b) { a.EnsureInitialized(); b.EnsureInitialized(); if (a.IsZero() && b.IsZero()) return 0; if (a.Sign != b.Sign) { //a and b have different signs if (a.Sign == 1) return 1; //every positive number is bigger than every negative number else return -1; //and vice versa } else if (a.NumDigits > b.NumDigits) { //a has more digits than b (a and b have same sign) return a.Sign; // If both are positive then a > b. If both are negative then a < b } else if (a.NumDigits < b.NumDigits) { //a has fewer digits than b (a and b have same sign) return (short)-a.Sign; // If both are positive then a < b. If both are negative then a > b } else { short aChunk, bChunk; for (int i = a.NumChunks - 1; i >= 0; i--) { aChunk = a.GetChunk(i); bChunk = b.GetChunk(i); if (aChunk < bChunk) return -1; else if (aChunk > bChunk) return 1; } //they must be equal return 0; } } public BigInt Abs() { EnsureInitialized(); BigInt result = this; if (result._sign == -1) result._sign = 1; return result; } 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; } public static void DivAndMod(BigInt a, BigInt b, out BigInt remainder, out BigInt result) { a.EnsureInitialized(); b.EnsureInitialized(); DivAndMod(DivModOperationType.DivAndMod, a, b, out remainder, out result); } /// /// Returns a number raised to a positive integer power. /// /// The BigInt to be raised to a power. /// The (non-negative) integer exponent. /// A BigInt containing the value of baseNum to the power of exponent. public static BigInt Power(BigInt baseNum, uint exponent) { if (exponent == 0) { return 1; } else if (exponent == 1) { return baseNum; } //Build up an array of [baseNum^1, baseNum^2, baseNum^4, baseNum^8 ....] //Then use the "bits" that make up the binary version of the exponent to tell us which // of these results to multiply to get baseNum ^ exponent. BigInt result = ((exponent & 1) == 0) ? 1 : baseNum; int maxRequired = (int)Math.Ceiling(Math.Log(exponent, 2)); BigInt[] arrBuildingBlockPowers = new BigInt[maxRequired + 1]; arrBuildingBlockPowers[0] = baseNum; for (int currBit = 1, currPower2 = 2; currPower2 <= exponent; currBit++, currPower2 = currPower2 << 1) { arrBuildingBlockPowers[currBit] = arrBuildingBlockPowers[currBit - 1] * arrBuildingBlockPowers[currBit - 1]; if ((exponent & currPower2) > 0) { result *= arrBuildingBlockPowers[currBit]; } } return result; } public static BigInt ModPower(BigInt a, BigInt b, BigInt c) { // returns (a ^ b) mod c // eg 28 ^ 23 mod 55 = 7 // There's a trick to this. a ^ b is usually way too big to handle, so we break it // into pieces, using the fact that xy mod c = ( x mod c )( y mod c ) mod c. // Please see www.carljohansen.co.uk/rsa for a full explanation. a.EnsureInitialized(); b.EnsureInitialized(); c.EnsureInitialized(); int numBdigits = b.NumDigits; BigInt prevResult, finalProduct = 1; int i; BigInt[] arrResidues = new BigInt[numBdigits + 1]; BigInt accumulation2, accumulation4, accumulation8; arrResidues[0] = a; prevResult = arrResidues[0]; for (i = 1; i < numBdigits; i++) { //have to calculate (prevResult^10) mod c. This is (prevResult^2^2^2) mod c * (prevResult^2) mod c accumulation2 = (prevResult * prevResult) % c; accumulation4 = (accumulation2 * accumulation2) % c; accumulation8 = (accumulation4 * accumulation4) % c; arrResidues[i] = (accumulation2 * accumulation8) % c; prevResult = arrResidues[i]; } for (i = numBdigits - 1; i >= 0; i--) { int currChunk = b.GetChunkAbsolute(i / 3); int currDigit; if (i % 3 == 0) currDigit = currChunk % 10; else if (i % 3 == 1) currDigit = (currChunk % 100) / 10; else currDigit = currChunk / 100; for (int j = 0; j < currDigit; j++) { finalProduct *= arrResidues[i]; finalProduct %= c; } } return finalProduct; } #endregion #region Public Operators public static implicit operator BigInt(int a) { //allows you to write this: BigInt myBigInt = 123456789; return new BigInt(a); } public static implicit operator BigInt(long a) { //allows you to write this: BigInt myBigInt = 123456789; return new BigInt(a); } public static implicit operator BigInt(string a) { //allows you to write this: BigInt myBigInt = "123456789012345678901234567890"; return new BigInt(a); } 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; } public static BigInt operator /(BigInt a, BigInt b) { a.EnsureInitialized(); b.EnsureInitialized(); BigInt result, dummy; DivAndMod(DivModOperationType.DivideOnly, a, b, out dummy, out result); return result; } public static BigInt operator %(BigInt a, BigInt b) { a.EnsureInitialized(); b.EnsureInitialized(); BigInt remainder, dummy; DivAndMod(DivModOperationType.ModulusOnly, a, b, out remainder, out dummy); return remainder; } public static BigInt operator ^(BigInt a, uint b) { a.EnsureInitialized(); return BigInt.Power(a, b); } public static BigInt operator -(BigInt a, BigInt b) { a.EnsureInitialized(); b.EnsureInitialized(); b._sign = (short)-b.Sign; // flip the sign of b BigInt result = a + b; // calculate (a) + (-b) b._sign = (short)-b.Sign; // put the sign back the way it was return result; } public static BigInt operator -(BigInt a) { //unary minus a.EnsureInitialized(); BigInt negA = a; if (!a.IsZero()) negA._sign = (short)-a._sign; return negA; } 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; } public static BigInt operator ++(BigInt a) { a.EnsureInitialized(); return a + 1; } public static BigInt operator --(BigInt a) { a.EnsureInitialized(); return a - 1; } 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 bool operator <(BigInt a, BigInt b) { return (Compare(a, b) < 0); } public static bool operator <=(BigInt a, BigInt b) { return (Compare(a, b) <= 0); } public static bool operator >(BigInt a, BigInt b) { return (Compare(a, b) > 0); } public static bool operator >=(BigInt a, BigInt b) { return (Compare(a, b) >= 0); } public static bool operator ==(BigInt a, BigInt b) { return (Compare(a, b) == 0); } public static bool operator !=(BigInt a, BigInt b) { return (Compare(a, b) != 0); } public override bool Equals(object obj) { return (this == (BigInt)obj); } public override int GetHashCode() { return this.ToString().GetHashCode(); } public override string ToString() { EnsureInitialized(); if (IsZero()) return ("0"); string result = String.Empty; for (int i = 0; i < NumChunks; i++) if (i < NumChunks - 1) result = System.String.Format("{0:000}", GetChunkAbsolute(i)) + result; else result = GetChunkAbsolute(i).ToString() + result; if (_sign == -1) result = "-" + result; return result; } #endregion #region Private Methods // This array says, for example, that private static ushort[,] mask = { { 0, 0, 0x03FF, 0x0000, 0xFC00, 0xFFFF }, // within a block of 8 chunks, (stored in 5 shorts) { 0, 10, 0xFC00, 0x000F, 0x03FF, 0xFFF0 }, // the fourth chunk spans short #1 and short #2 { 1, 4, 0x3FF0, 0x0000, 0xC00F, 0xFFFF }, // To obtain it, combine short #2 and short #1 to { 1, 14, 0xC000, 0x00FF, 0x3FFF, 0xFF00 }, // make an int, and mask the int with 0x00FFC000. { 2, 8, 0xFF00, 0x0003, 0x00FF, 0xFFFC }, // Shift the result right 14 bits to get the { 3, 2, 0x0FFC, 0x0000, 0xF003, 0xFFFF }, // the fourth chunk. Before setting it, AND short #1 with { 3, 12, 0xF000, 0x003F, 0x0FFF, 0xFFC0 }, // 0x3FFF (= ~C000) to zero out the current values of those bits. { 4, 6, 0xFFC0, 0x0000, 0x003F, 0xFFFF } };// Note that all the masks are ten consecutive set bits. private static int StorageSizeForDigits(int numDigits) { //Every three digits requires 10 bits. int numBits = ((numDigits / 3) + 1) * 10; //Translate this into a number of ushorts. // (Note that we add an extra byte to make things easier // because chunks can span two ushorts). return (numBits / 16) + 2; } private void InitBigIntFromString(string initialValue) { if (initialValue == null) { throw new ArgumentNullException("initialValue"); } if (!Regex.IsMatch(initialValue, "^[-+]?([0-9])+$")) throw (new ArgumentException("Value must be a valid integer", "initialValue")); if (_initialCapacity == 0 || _bitblock == null) { _initialCapacity = StorageSizeForDigits(initialValue.Length); _bitblockArraySize = _initialCapacity; _bitblock = new ushort[_bitblockArraySize]; } SetToEmpty(); if (initialValue.StartsWith("-")) { _sign = (short)-1; initialValue = initialValue.Remove(0, 1); } else { _sign = (short)1; } int currStringPos, currChunkLength; if (initialValue.Length <= 3) { currStringPos = 0; currChunkLength = initialValue.Length; } else { currStringPos = initialValue.Length - 3; currChunkLength = 3; } do { ushort nextChunk = 0; for (int i = currChunkLength - 1, j = 1; i >= 0; i--, j *= 10) { nextChunk += (ushort)(j * Convert.ToUInt16(initialValue[currStringPos + i] - '0')); } AppendChunk(nextChunk); currStringPos -= 3; if (currStringPos < 0) { currChunkLength = 3 + currStringPos; currStringPos = 0; } } while (currChunkLength > 0); AfterUpdate(); } private void AppendChunk(ushort threeDigitValue) { //adds a new chunk (3 digit number) as the most significant chunk int blockNum = _numChunks / 8; //each block holds 8 chunks. int blockOffset = _numChunks - (blockNum * 8); ushort startByte = mask[blockOffset, 0]; ushort startBitWithinStartByte = mask[blockOffset, 1]; int destStartIndex = blockNum * 5 + startByte; //each block is 5 ushorts (10 bytes) in size. ushort maskWithinStartByte = mask[blockOffset, 2]; ushort maskWithinSecondByte = mask[blockOffset, 3]; uint valueToMask = (uint)threeDigitValue << startBitWithinStartByte; ushort startByteMaskComplement = mask[blockOffset, 4]; //used to zero out the blocks ushort secondByteMaskComplement = mask[blockOffset, 5]; // we are about to replace ushort maskedValueInStartByte = (ushort)(valueToMask & maskWithinStartByte); ushort maskedValueInSecondByte = (ushort)((valueToMask >> 16) & maskWithinSecondByte); if (destStartIndex >= (_bitblockArraySize - 1)) { //We have reached the limit of the current array allocation, so allocate some more. _bitblockArraySize += ARRAY_ALLOC_SIZE; Array.Resize(ref _bitblock, _bitblockArraySize); } _bitblock[destStartIndex] = (ushort)((_bitblock[destStartIndex] & startByteMaskComplement) | maskedValueInStartByte); _bitblock[destStartIndex + 1] = (ushort)((_bitblock[destStartIndex + 1] & secondByteMaskComplement) | maskedValueInSecondByte); _numChunks++; } private short GetChunkAbsolute(int chunkIndex) { if (chunkIndex >= NumChunks) return 0; int blockNum = chunkIndex / 8; int blockOffset = chunkIndex - (blockNum * 8); ushort startByte = mask[blockOffset, 0]; ushort startBitWithinStartByte = mask[blockOffset, 1]; ushort maskWithinStartByte = mask[blockOffset, 2]; ushort maskWithinSecondByte = mask[blockOffset, 3]; ushort val1 = (ushort)(_bitblock[blockNum * 5 + startByte] & maskWithinStartByte); ushort val2 = (ushort)(_bitblock[blockNum * 5 + startByte + 1] & maskWithinSecondByte); return (short)((val1 >> startBitWithinStartByte) | (val2 << (16 - startBitWithinStartByte))); } private short GetChunk(int chunkIndex) { if (_sign == 1) return GetChunkAbsolute(chunkIndex); else return (short)-GetChunkAbsolute(chunkIndex); } private void AfterUpdate() { while (_numChunks > 0 && GetChunk(_numChunks - 1) == 0) _numChunks--; //Remove zero chunks from LHS if (_numChunks == 0) { SetToEmpty(); } else { _numDigits = (_numChunks - 1) * 3; short lastChunk = GetChunk(_numChunks - 1); if (_sign == -1) lastChunk = (short)-lastChunk; if (lastChunk > 99) _numDigits += 3; else if (lastChunk > 9) _numDigits += 2; else _numDigits++; } } /// /// Compares the absolute values of two BigInts /// /// 1 if |a| > |b|, 0 if |a| == |b|, -1 if |a| is less than |b| private static short CompareMagnitudes(BigInt a, BigInt b) { if (a.IsZero() && b.IsZero()) return 0; if (a.NumDigits > b.NumDigits) { //a has more digits than b so it must be bigger return (short)1; } else if (a.NumDigits < b.NumDigits) { //a has fewer digits than b so it must be smaller return (short)-1; } else { short aChunk, bChunk; for (int i = a.NumChunks - 1; i >= 0; i--) { aChunk = a.GetChunkAbsolute(i); bChunk = b.GetChunkAbsolute(i); if (aChunk < bChunk) return -1; else if (aChunk > bChunk) return 1; } //they must be equal return 0; } } private bool IsInitialized() { return (_bitblock != null) && (_bitblock.Length > 0); } private void EnsureInitialized() { if (!IsInitialized()) SetToEmpty(); } private void SetToEmpty() { _sign = 1; _numChunks = 0; _numDigits = 0; } private bool IsZero() { if (_numChunks > 1) return false; else if (!IsInitialized()) return true; else return (_bitblock[0] == 0); } private static int MaxNumChunks(BigInt a, BigInt b) { if (a.NumChunks > b.NumChunks) return a.NumChunks; else return b.NumChunks; } private static int MinNumChunks(BigInt a, BigInt b) { if (a.NumChunks < b.NumChunks) return a.NumChunks; else return b.NumChunks; } 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; } private static void DivAndMod(DivModOperationType operationType, BigInt a, BigInt b, out BigInt remainder, out BigInt result) // sets to the result of dividing a by b, and sets 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(); } } #endregion } }