2010-12-09 22 views
6

En C# cuando realiza la división de dos decimales, el último dígito del resultado se redondeará automáticamente si el resultado matemático verdadero no se puede almacenar exactamente como un tipo de decimal.C# Cómo dividir decimales sin redondeo implícito

Quiero escribir una función que realiza la división donde el último dígito siempre se redondea hacia abajo, incluso si el dígito a la derecha del último normalmente provocaría un redondeo.

Mi función sería declarado como MyDivide (decimal a, b decimal)

Como ejemplo MyDivide (2,0 M, 3,0 M) => ,6666666666666666666666666666

mientras que el operador C# división produciría 2,0 M/3.0M => 0.6666666666666666666666666667

Se agradece cualquier ayuda para implementar esto.

+0

La implementación de la idea de espacio de oficina para robar rebanada salami y fracciones de un centavo? No estropees el punto decimal IMO. – jason

+0

El algoritmo de división redondea desde cero, no hacia arriba. ¿Presumiblemente quieres un algoritmo que se redondee hacia cero? – Gabe

Respuesta

3

Vas a tener que implementar long division tú mismo. No hay forma de arreglar la división incorporada para que haga esto por usted (pista: no se puede distinguir entre 2m/3m y .6666666666666666666666666667m/1m o 6666666666666666666666666667m/10000000000000000000000000000m para el caso).

+0

Pensé que podría necesitar hacer esto, así que traté de usar los proyectos en http://sine.codeplex.com/ y http://www.fractal-landscapes.co.uk/bigint.html El primero parecía tener errores y el segundo proyecto hizo el redondeo igual que el operador de división de C#. Pude modificar el código para que no sea redondo, pero todavía estoy bastante nervioso al respecto. –

2

No hay una manera fácil de dividirlo como desea, pero puede detectar el redondeo y corregirlo. Como la mantisa de decimal es de 96 bits, no puede contenerla en un long o double, entonces utilizo un objeto .NET 4 BigInteger. Simplemente multiplico las mantisas del denominador y el cociente y las comparo contra el numerador (ajustado para el exponente del resultado de la multiplicación). Si el resultado es mayor que el numerador, entonces la división debe haberse redondeado desde cero, por lo que solo restaría 1 en la posición menos significativa del cociente. Para hacer esto, creo un decimal con 1 para una mantisa y el exponente del cociente para su exponente.

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)); 
     } 
    } 
} 

La salida del programa anterior es:

 
Divide: 0.6666666666666666666666666667 
MyDivide: 0.6666666666666666666666666666 
+0

¿Puede 'decimal' codificar 2/3 con precisión o es el resultado anterior el resultado de algún formato de redondeo/visualización? –

+0

pst: 'decimal' es, como su nombre indica, un formato decimal. Como tal, no puede codificar 2/3 con precisión, pero codifica exactamente 0.6666666666666666666666666667 y 0.6666666666666666666666666666 (ambos elegidos por el ejemplo de OP). – Gabe

+0

Gracias por la solución detallada. Lamentablemente, nuestro proyecto no utiliza 4.0 –