private static int RoundDigit(int sign, uint lastDigit, uint secondLastDigit, BigDecimal.RoundingModes roundingMode) { int result = -1; switch (roundingMode) { case BigDecimal.RoundingModes.Up: if (lastDigit != 0) { result = 1; } break; case BigDecimal.RoundingModes.HalfUp: if (lastDigit >= 5) { result = 1; } break; case BigDecimal.RoundingModes.HalfDown: if (lastDigit > 5) { result = 1; } break; case BigDecimal.RoundingModes.HalfEven: if (lastDigit > 5) { result = 1; } else if ((lastDigit == 5) && (secondLastDigit % 2 != 0)) { result = 1; } break; case BigDecimal.RoundingModes.Ceiling: if (sign == 1 && lastDigit != 0) { result = 1; } break; case BigDecimal.RoundingModes.Floor: if (sign == -1 && lastDigit != 0) { result = 1; } break; } return(result); }
/// <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); }