开发者

C# How to divide decimals without implicit round up

开发者 https://www.devze.com 2023-01-29 08:59 出处:网络
In c# when you perform the division of two decimals the last digit of the result will automatically be rounded if the true mathematical result cannot be exactly stored as a decimal type.

In c# when you perform the division of two decimals the last digit of the result will automatically be rounded if the true mathematical result cannot be exactly stored as a decimal type.

I want to write a function that performs the division where the last digit is always rounded down even if the digit to the right of the last would normally cause a round up.

My function would be declared as MyDivide(decimal a, decimal b)

As an example MyDivide(2.0M, 3.0M) => 0.6666666666666666666666666666

whereas the c# division operator would yie开发者_StackOverflow社区ld 2.0M / 3.0M => 0.6666666666666666666666666667

Any help implementing this is appreciated.


You're going to have to implement long division yourself. There is NO way to finagle the built-in division into doing this for you (hint: you can't distinguish between 2m / 3m and .6666666666666666666666666667m / 1m or 6666666666666666666666666667m / 10000000000000000000000000000m for that matter).


There's no easy way to get it to divide the way you want, but you can detect the rounding and correct for it. Since the decimal's mantissa is 96 bits you cannot hold it in a long or double, so I use a .Net 4 BigInteger object. I just multiply the mantissas of the denominator and quotient and compare it against the numerator (adjusted for the exponent of the result of the multiplication). If the result is greater than the numerator then the division must have rounded away from zero, so I just have to subtract a 1 in the least significant position from the quotient. To do this I create a decimal with 1 for a mantissa and the quotient's exponent for its exponent.

using System;
using System.Numerics;
using System.Collections.Generic;

namespace DivTest
{
    class Divide
    {
        public static decimal MyDivide(decimal numerator, decimal denominator)
        {
            var quotient = numerator / denominator;
            // turn decimals into mantissas (BigInts) and exponents
            int nExp, dExp, qExp;
            var nMan = MantissaOfDecimal(num, out nExp);
            var dMan = MantissaOfDecimal(denom, out dExp);
            var qMan = MantissaOfDecimal(quotient, out qExp);
            // multiply quotient times denominator and compare with numerator
            if (dMan * qMan > nMan * BigInteger.Pow(10, dExp + qExp - nExp))
            {
                // quotient was rounded away from zero, so subtract LSB
                // to round back toward zero
                quotient -= new decimal(1, 0, 0, quotient < 0, (byte)qExp);
            }
            return quotient;
        }

        static BigInteger MantissaOfDecimal(decimal d, out int exponent)
        {
            var ints = decimal.GetBits(d);
            exponent = (ints[3] >> 16) & 0xFF;
            var bytes = new List<byte>(13);
            // create a BigInteger containing the mantissa of the decimal value
            bytes.AddRange(BitConverter.GetBytes(ints[0]));
            bytes.AddRange(BitConverter.GetBytes(ints[1]));
            bytes.AddRange(BitConverter.GetBytes(ints[2]));
            bytes.Add(0); // avoid high bits being interpreted as negative
            return new BigInteger(bytes.ToArray());
        }

        static void Main()
        {
            decimal num = 2m, denom = 3m;
            Console.WriteLine("Divide:   " + num / denom);
            Console.WriteLine("MyDivide: " + MyDivide(num, denom));
        }
    }
}

The output of the above program is:

Divide:   0.6666666666666666666666666667
MyDivide: 0.6666666666666666666666666666
0

精彩评论

暂无评论...
验证码 换一张
取 消