public Fraction(Fraction/*!*/ copyFrom, int maxPrecision) : this(maxPrecision, maxPrecision) { // Copy the fraction int length = System.Math.Min(maxPrecision, copyFrom.MaxPrecision); Array.Copy(copyFrom._words, _words, length); }
private static Fraction/*!*/ Normalize(Fraction/*!*/ f, out int digitOffset) { // Count leading zero words int leadingZeroWords = 0; while (leadingZeroWords < f.MaxPrecision && f._words[leadingZeroWords] == 0) { leadingZeroWords++; } if (leadingZeroWords == f.MaxPrecision) { // The fraction is just all zeros digitOffset = 0; return f; } // Count trailing zero words int trailingZeroWords = 0; while (f._words[f.Precision - 1 - trailingZeroWords] == 0) { trailingZeroWords++; } // Count leading zero digits (within the first non-zero word) // and also calculate up and down factors for masking int leadingZeroDigits = BASE_FIG; uint firstWord = f._words[leadingZeroWords]; uint downFactor = 1; while (firstWord != 0) { firstWord /= 10; leadingZeroDigits--; downFactor *= 10; } uint upFactor = BASE / downFactor; int newPrecision = f.Precision - leadingZeroWords - trailingZeroWords; Fraction n = new Fraction(newPrecision); // Copy the non-zero words across Array.Copy(f._words, leadingZeroWords, n._words, 0, n.MaxPrecision); if (leadingZeroDigits > 0) { // Scroll the digits within the non-zero words to trim off the first zero digits uint bottomDigits = n._words[0] * upFactor; uint topDigits = 0; int wordIndex = 1; while (wordIndex < n.Precision) { topDigits = n._words[wordIndex] / downFactor; n._words[wordIndex - 1] = bottomDigits + topDigits; bottomDigits = (n._words[wordIndex] % downFactor) * upFactor; wordIndex++; } // Fix up the last word n._words[wordIndex - 1] = bottomDigits; // and the Precision n.Precision = wordIndex; } // Return the offset in digits caused by the normalization. digitOffset = -(leadingZeroWords * BASE_FIG + leadingZeroDigits); return n; }
private static Fraction/*!*/ Parse(string/*!*/ digits, out int digitOffset) { // Trim off any trailing zeros digits = digits.TrimEnd('0'); if (digits == "") { digitOffset = 0; return Zero; } // Create the new fraction int precision = digits.Length / BASE_FIG; int finalDigits = digits.Length % BASE_FIG; if (finalDigits > 0) { ++precision; } Fraction/*!*/ fraction = new Fraction(precision); // Iterate through groups of BASE_FIG digits int digitIndex; int wordIndex = 0; for (digitIndex = 0; digitIndex + BASE_FIG <= digits.Length; digitIndex += BASE_FIG) { fraction._words[wordIndex] = uint.Parse(digits.Substring(digitIndex, BASE_FIG), CultureInfo.InvariantCulture); wordIndex++; } // Add on the last few digits, adding extra zeros as necessary if (finalDigits > 0) { uint lastWord = uint.Parse(digits.Substring(digitIndex), CultureInfo.InvariantCulture); while (finalDigits < BASE_FIG) { lastWord *= 10; ++finalDigits; } fraction._words[wordIndex] = lastWord; } fraction = Fraction.Normalize(fraction, out digitOffset); return fraction; }
/// <summary> /// Limits the precision of the given Fraction. /// </summary> /// <param name="sign">The sign of the BigDecimal</param> /// <param name="fraction">The fraction to limit</param> /// <param name="digits">The number of digits to which we are limiting</param> /// <param name="mode">The rounding mode to use when limiting</param> /// <returns>A new fraction that has no more than <paramref name="digits"/> digits.</returns> /// <example> /// Consider a fraction of 123456789 using default HalfUp rounding. /// Limit : Result /// 1 1 /// 2 12 /// 3 123 /// 4 1234 /// 5 12346 /// 6 123457 /// 7 1234568 /// 8 12345679 /// 9 123456789 /// 10 123456789 /// </example> public static Fraction/*!*/ LimitPrecision(int sign, Fraction/*!*/ fraction, int digits, BigDecimal.RoundingModes mode, out int offset) { Fraction result; if (digits <= 0) { uint digit = (uint)(fraction.IsZero ? 0 : 1); if (RoundDigit(sign, digit, 0, mode) > 0) { offset = 1 - digits; return One; } else { offset = 0; return Zero; } } if (digits >= fraction.DigitCount) { offset = 0; return fraction; } // Calculate offsets of relevant digits int secondLastDigitIndex; // i.e. fraction[digits-1] int secondLastWordIndex; uint secondLastDigit; int lastDigitIndex; // i.e. fraction[digits] int lastWordIndex; uint lastDigit; #if SILVERLIGHT secondLastWordIndex = DivRem(digits - 1, BASE_FIG, out secondLastDigitIndex); #else secondLastWordIndex = System.Math.DivRem(digits - 1, BASE_FIG, out secondLastDigitIndex); #endif if (secondLastDigitIndex == BASE_FIG - 1) { lastWordIndex = secondLastWordIndex + 1; lastDigitIndex = 0; } else { lastWordIndex = secondLastWordIndex; lastDigitIndex = secondLastDigitIndex + 1; } // TODO: Extract these calculations out into static readonly arrays // Mask for last digit. E.g. lastDigitIndex = 3, BASE_FIG = 9 => lastFactor = 1000000 uint lastFactor = _powers[lastDigitIndex]; // Mask for second last digit uint secondLastFactor = _powers[secondLastDigitIndex]; // Calculate the current digits and rounding direction secondLastDigit = (fraction._words[secondLastWordIndex] / secondLastFactor) % 10; if (lastWordIndex < fraction.MaxPrecision) { lastDigit = (fraction._words[lastWordIndex] / lastFactor) % 10; } else { lastDigit = 0; } int round = RoundDigit(sign, lastDigit, secondLastDigit, mode); // Create a temporary fraction used to cause the rounding in the original result = new Fraction(lastWordIndex + 1); Array.Copy(fraction._words, 0, result._words, 0, System.Math.Min(lastWordIndex + 1, fraction.Precision)); // Clear the digits beyond the second last digit result._words[secondLastWordIndex] = result._words[secondLastWordIndex] - (fraction._words[secondLastWordIndex] % secondLastFactor); if (round > 0) { // Increment the last digit of result by 1 Fraction temp = new Fraction(secondLastWordIndex + 1); temp._words[secondLastWordIndex] = secondLastFactor; result = Fraction.Add(result, temp, 0, out offset); } else { offset = 0; } result.Precision = System.Math.Min(secondLastWordIndex + 1, result.MaxPrecision); return result; }
// Note: Scrolling left is just the same as scrolling right by (BASE_FIG-offset) private static Fraction/*!*/ ScrollRight(Fraction/*!*/ fraction, int offset) { Debug.Assert(offset <= BASE_FIG); Debug.Assert(offset >= 0); // Don't do anything if offset is not going to change the internal digit layout if (offset % BASE_FIG == 0) { return fraction; } int oldPrecision = fraction.Precision; int newPrecision = oldPrecision + 1; Fraction newFraction = new Fraction(newPrecision); // Calculate masking values // divisor is used to mask off and bring top digits down to bottom digits (TTTTTTBBBB / divisor == TTTTTT) // also divisor is used to mask off the bottom digits (TTTTTTBBBB % divisor == BBBB) // factor is then used to bring bottom digits up to top digits (BBBB * factor == BBBB000000) uint downFactor = 1; for (int i = 0; i < offset; ++i) { downFactor *= 10; } uint upFactor = BASE / downFactor; // Scroll the digits // We want to go from TTTTTTBBBB TTTTTTBBBB TTTTTTBBBB to 0000TTTTTT BBBBTTTTTT BBBBTTTTTT BBBB000000 // I.E. We are pushing all the digits to the right by "offset" amount and padding with zeros uint topDigits = 0; uint bottomDigits = 0; int wordIndex = 0; while (wordIndex < oldPrecision) { topDigits = fraction._words[wordIndex] / downFactor; newFraction._words[wordIndex] = bottomDigits + topDigits; bottomDigits = (fraction._words[wordIndex] % downFactor) * upFactor; wordIndex++; } // Fix up the last word newFraction._words[wordIndex] = bottomDigits; return newFraction; }
public static int Compare(Fraction/*!*/ x, Fraction/*!*/ y, int exponentDiff) { if (exponentDiff != 0) { return exponentDiff > 0 ? 1 : -1; } int wordIndex = 0; while (wordIndex < x.Precision && wordIndex < y.Precision) { if (x._words[wordIndex] != y._words[wordIndex]) { return x._words[wordIndex] > y._words[wordIndex] ? 1 : -1; } wordIndex++; } if (wordIndex == x.Precision) { while (wordIndex < y.Precision) { if (y._words[wordIndex] != 0) { return -1; } wordIndex++; } } else { while (wordIndex < x.Precision) { if (x._words[wordIndex] != 0) { return 1; } wordIndex++; } } return 0; }
public static Fraction/*!*/ Divide(Fraction/*!*/ a, Fraction/*!*/ b, int minPrecision, out Fraction/*!*/ r, out int cOffset, out int rOffset) { int precision = System.Math.Max(a.MaxPrecision + b.MaxPrecision + 1, minPrecision); Fraction c = new Fraction(precision); r = new Fraction(precision * 2); uint[] aWords = a._words; uint[] bWords = b._words; uint[] cWords = c._words; uint[] rWords = r._words; int aSize = a.Precision; int bSize = b.Precision; int cSize = c.MaxPrecision; int rSize = r.MaxPrecision; // Initialize remainder (we add an extra word at the front to catch the overflow Array.Copy(aWords, 0, rWords, 1, System.Math.Min(aSize, rSize - 1)); // Setup basic values ulong b1 = bWords[0]; ulong b1plus1 = bSize <= 1 ? b1 : b1 + 1; ulong b1b2 = bSize <= 1 ? (ulong)bWords[0] * BASE : GetDoubleWord(bWords, 0, b.Precision); ulong b1b2plus1 = bSize <= 2 ? b1b2 : b1b2 + 1; // Main loop int index = 1; int size = System.Math.Min(cSize, rSize); while (index < size) { if (rWords[index] == 0) { ++index; continue; } ulong r1r2 = GetDoubleWord(rWords, index, rSize); if (r1r2 == b1b2) { // Iterate through the rest of b comparing words of r and b until they are not equal int bIndex = 2; int rIndex = index + 2; FindNextNonEqual(rWords, bWords, bSize, ref rIndex, ref bIndex); if (rIndex < rSize && bIndex < bSize && rWords[rIndex] < bWords[bIndex]) { if (index + 1 > rSize) { break; } InternalDivide(rWords, rSize, r1r2 / b1plus1, bWords, bSize, index, cWords); } else { // Quotient is 1, just subtract b from r SubtractInPlace(rWords, bWords, bSize, index); cWords[index - 1]++; Carry(cWords, index); } } else if (r1r2 >= b1b2plus1) { InternalDivide(rWords, rSize, r1r2 / b1b2plus1, bWords, bSize, index - 1, cWords); } else { InternalDivide(rWords, rSize, r1r2 / b1plus1, bWords, bSize, index, cWords); } } c = Normalize(c, out cOffset); r = Normalize(r, out rOffset); // We added artificially an extra word onto r and c to cope with carry overflow (so take away again now) cOffset += BASE_FIG; rOffset += BASE_FIG; return c; }
public static Fraction/*!*/ Multiply(Fraction/*!*/ x, Fraction/*!*/ y, out int offset) { int xPrecision = x.Precision; int yPrecision = y.Precision; int zPrecision = xPrecision + yPrecision; uint[] xData = x._words; uint[] yData = y._words; uint[] zData = new uint[zPrecision]; Fraction z = new Fraction(zData); for (int xIndex = xPrecision - 1; xIndex >= 0; xIndex--) { uint xValue = xData[xIndex]; int zIndex = zPrecision - (xPrecision - xIndex); ulong carry = 0; for (int yIndex = yPrecision - 1; yIndex >= 0; yIndex--) { carry = carry + ((ulong)xValue) * yData[yIndex] + zData[zIndex]; zData[zIndex--] = (uint)(carry % BASE); carry /= BASE; } while (carry != 0) { carry += zData[zIndex]; zData[zIndex--] = (uint)carry; carry /= BASE; } } z = Fraction.Normalize(z, out offset); return z; }
public static Fraction/*!*/ Subtract(Fraction/*!*/ x, Fraction/*!*/ y, int exponentDiff, out int exponentOffset, out int sign) { Fraction upper = x; Fraction lower = y; sign = Compare(x, y, exponentDiff); if (sign == 0) { exponentOffset = 0; return new Fraction(1); } else if (sign < 0) { exponentDiff = -exponentDiff; upper = y; lower = x; } // Calculate the word and digit offsets between upper and lower int wordOffset = exponentDiff / BASE_FIG; int digitOffset = exponentDiff % BASE_FIG; // If necessary we need to scroll one of the arrays if (digitOffset != 0) { lower = ScrollRight(lower, digitOffset); } int lowerStart = wordOffset; // We should now have something like: // UUU UUU UU0 000 000 000 (upperStart=0, upperLength=8) // 000 0LL LLL LLL LLL LL0 (lowerStart=4, lowerLength=13) // assuming that exponentDiff is 4 (or -4) and BASE_FIG is 3 // where each character above is a decimal digit and a space indicates a word boundary // Also, upper should be larger than lower int zPrecision = System.Math.Max(upper.Precision, lower.Precision + lowerStart); Fraction z = new Fraction(zPrecision); uint[] upperWords = upper._words; uint[] lowerWords = lower._words; uint[] zWords = z._words; // Copy words of upper straight into z Array.Copy(upperWords, 0, zWords, 0, upper.Precision); //// Subtract words of lower from z, borrowing as necessary SubtractInPlace(zWords, lowerWords, lower.Precision, lowerStart); z = Fraction.Normalize(z, out exponentOffset); return z; }
public static Fraction/*!*/ Add(Fraction/*!*/ x, Fraction/*!*/y, int exponentDiff, out int exponentOffset) { Fraction upper = x; Fraction lower = y; // Switch x and y around if the exponentDiff is negative bool swap = exponentDiff < 0; if (swap) { exponentDiff = -exponentDiff; upper = y; lower = x; } // Calculate the word and digit offsets between upper and lower int wordOffset = exponentDiff / BASE_FIG; int digitOffset = exponentDiff % BASE_FIG; // If necessary we need to scroll one of the arrays if (digitOffset != 0) { lower = ScrollRight(lower, digitOffset); } int upperStart = 0; int lowerStart = wordOffset; // We should now have something like: // UUU UUU UU0 000 000 000 (upperStart=0, upperLength=8) // 000 0LL LLL LLL LLL LL0 (lowerStart=4, lowerLength=13) // assuming that exponentDiff is 4 (or -4) and BASE_FIG is 3 // where each character above is a decimal digit and a space indicates a word boundary int zPrecision = System.Math.Max(upper.Precision, lower.Precision + wordOffset) + 1; Fraction z = new Fraction(zPrecision); uint[] upperWords = upper._words; uint[] lowerWords = lower._words; uint[] zWords = z._words; // Copy words of lower straight into z Array.Copy(lowerWords, 0, zWords, lowerStart + 1, lower.Precision); // Add words of upper into z, carrying as necessary ulong carry = 0; for (int i = upper.Precision - 1; i >= upperStart; i--) { carry += upperWords[i] + zWords[i + 1]; zWords[i + 1] = (uint)(carry % BASE); carry /= BASE; } Debug.Assert(carry / BASE == 0); zWords[0] = (uint)(carry % BASE); // We expect there to be BASE_FIG offset when normalizing unless // the carry overflowed into the top word. z = Fraction.Normalize(z, out exponentOffset); exponentOffset += BASE_FIG; return z; }