这是我对类似于 Excel ROUNDUP 函数的解决方案的尝试。我试图涵盖以下情况:负十进制数、负数(是的 Excel 支持)、大十进制值
public static decimal RoundUp(decimal number, int digits)
{
if (digits > 0)
{
// numbers will have a format like +/-1.23, where the fractional part is optional if numbers are integral
// Excel RoundUp rounds negative numbers as if they were positive.
// To simulate this behavior we will use the absolute value of the number
// E.g. |1.23| = |-1.23| = 1.23
var absNumber = Math.Abs(number);
// Now take the integral part (E.g. for 1.23 is 1)
var absNumberIntegralPart = Decimal.Floor(absNumber);
// Now take the fractional part (E.g. for 1.23 is 0.23)
var fraction = (absNumber - absNumberIntegralPart);
// Multiply fractional part by the 10 ^ number of digits we're rounding to
// E.g. For 1.23 with rounded to 1 digit it will be 0.23 * 10^1 = 2.3
// Then we round that value UP using Decimal.Ceiling and we transform it back to a fractional part by dividing it by 10^number of digits
// E.g. Decimal.Ceiling(0.23 * 10) / 10 = Decimal.Ceiling(2.3) / 10 = 3 / 10 = 0.3
var tenPower = (decimal)Math.Pow(10, digits);
var fractionRoundedUp = Decimal.Ceiling(fraction * tenPower) / tenPower;
// Now we add up the absolute part with the rounded up fractional part and we put back the negative sign if needed
// E.g. 1 + 0.3 = 1.3
return Math.Sign(number) * (absNumberIntegralPart + fractionRoundedUp);
} else if (digits == 0)
{
return Math.Sign(number) * Decimal.Ceiling(Math.Abs(number));
} else if (digits < 0)
{
// negative digit rounding means that for RoundUp(149.12, -2) we will discard the fractional part, shift the decimal point on the left part 2 places before rounding up
// then replace all digits on the right of the decimal point with zeroes
// E.g. RoundUp(149.12, -2). Shift decimal point 2 places => 1.49. Now roundup(1.49) = 2 and we put 00 instead of 49 => 200
var absNumber = Math.Abs(number);
var absNumberIntegralPart = Decimal.Floor(absNumber);
var tenPower = (decimal)Math.Pow(10, -digits);
var absNumberIntegraPartRoundedUp = Decimal.Ceiling(absNumberIntegralPart / tenPower) * tenPower;
return Math.Sign(number)*absNumberIntegraPartRoundedUp;
}
return number;
}
[TestMethod]
public void Can_RoundUp_Correctly()
{
Assert.AreEqual(1.466m, MathExtensions.RoundUp(1.4655m, 3));
Assert.AreEqual(-1.466m, MathExtensions.RoundUp(-1.4655m, 3));
Assert.AreEqual(150m, MathExtensions.RoundUp(149.001m, 0));
Assert.AreEqual(-150m, MathExtensions.RoundUp(-149.001m, 0));
Assert.AreEqual(149.2m, MathExtensions.RoundUp(149.12m, 1));
Assert.AreEqual(149.12m, MathExtensions.RoundUp(149.12m, 2));
Assert.AreEqual(1232m, MathExtensions.RoundUp(1232, 2));
Assert.AreEqual(200m, MathExtensions.RoundUp(149.123m, -2));
Assert.AreEqual(-200m, MathExtensions.RoundUp(-149.123m, -2));
Assert.AreEqual(-20m, MathExtensions.RoundUp(-12.4655m, -1));
Assert.AreEqual(1.67m, MathExtensions.RoundUp(1.666666666666666666666666666m, 2));
Assert.AreEqual(1000000000000000000000000000m, MathExtensions.RoundUp(999999999999999999999999999m, -2));
Assert.AreEqual(10000000000000m, MathExtensions.RoundUp(9999999999999.999m, 2));
}