2010-01-25 31 views
31

Quiero un método de redondeo en valores dobles en C#. Necesita poder redondear un valor doble a cualquier valor de precisión de redondeo. Mi código en la mano se ve así:Redondeo de valores dobles en C#

public static double RoundI(double number, double roundingInterval) { 

    if (roundingInterval == 0.0) 
    { 
     return; 
    } 

    double intv = Math.Abs(roundingInterval); 
    double sign = Math.Sign(number); 
    double val = Math.Abs(number); 

    double valIntvRatio = val/intv; 
    double k = Math.Floor(valIntvRatio); 
    double m = valIntvRatio - k; 

    bool mGreaterThanMidPoint = ((m - 0.5) >= 1e-14) ? true : false; 
    bool mInMidpoint = (Math.Abs(m - 0.5) < 1e-14) ? true : false; 
    return (mGreaterThanMidPoint || mInMidpoint) ? sign * ((k + 1) * intv) : sign * (k * intv); 
} 

Así fase en el (100, 3) debe dar 99 y fase en el (1,2345, 0,001) debe dar 1,235.

El problema es que RoundI (1.275, 0.01) devuelve 1.27, en lugar de 1.28. Esto se debe a que al ejecutar double valIntvRatio = val/intv, es decir, double valIntvRatio = 1.275/0.01, da 0.12749999999999. Sé que este es un problema con la doble representación en cualquier lenguaje de programación. Mi pregunta es, ¿existe un código estándar para hacer cosas como esta, sin la necesidad de preocuparse por la precisión en doble? Aquí configuro el tolerante a 1e-14, pero esto es demasiado restringido para este problema y no sé cuál es la tolerancia correcta que se debe establecer. Gracias por cualquier ayuda.

+5

Quizás debería considerar utilizar el tipo de datos Decimal. – Kibbee

+0

¿Por qué la ronda (100,3) daría 99? Si está redondeando a la misma posición fraccionaria que 3 (0 lugares), obtendría 100, no 99. – paxdiablo

+0

paxdiablo: lo siento, el propósito de RoundI no es redondear para redondear el primer parámetro a la misma posición fraccionaria como el segundo parámetro. El segundo parámetro es el intervalo de ronda y el redondeo redondea el primer parámetro al valor de armario que tiene el modo 0 con respecto al segundo parámetro. – Steve

Respuesta

41

Ejemplo de uso de decimal, como Kibbee señaló

double d = 1.275; 
Math.Round(d, 2);   // 1.27 
Math.Round((decimal)d, 2); // 1.28 
+1

Si bien esta gran solución generalmente funciona, cuando los dígitos significativos de 'd' están cerca de la limitación de un' doble', la conversión a 'decimal' en realidad elimina demasiada precisión. Como ejemplo, tome 'd = 123456789.256;' (debe generar esto con 'd.ToString (" R ")' para revelar la precisión "oculta"). Si usa simplemente 'Math.Round (d, 2)' en este ejemplo (recuerde escribir el resultado con '" R "') obtendrá un resultado mejor que si lo hace 'Math.Round ((decimal) d, 2) '.Esa conversión a 'decimal' elimina demasiada precisión aquí. En tales casos, use 'decimal' desde el principio, sin moldes. –

+0

+1 solución muy agradable, pero desafortunadamente no redondea ** 0.005 ** a ** 0.01 **. El resultado es ** 0 ** –

+8

@Zefnus porque usa el redondeo del banco. Si desea redondear a 0.01, use 'Math.Round ((decimal) d, 2, MidpointRounding.AwayFromZero)' – Jimmy

5
double d = 1.2345; 

Math.Round(d, 2); 

el código anterior debe hacer el truco.

2

Si realmente necesita usar double simplemente reemplácelo a continuación y funcionará pero con los problemas de precisión habituales de la aritmética binaria en coma flotante.

Sin duda hay una mejor manera de implementar el "redondeo" (casi una especie de redondeo de banqueros) que mi malabares de cuerdas a continuación.

public static decimal RoundI(decimal number, decimal roundingInterval) 
{ 
    if (roundingInterval == 0) { return 0;} 

    decimal intv = Math.Abs(roundingInterval); 
    decimal modulo = number % intv; 
    if ((intv - modulo) == modulo) { 
     var temp = (number - modulo).ToString("#.##################"); 
     if (temp.Length != 0 && temp[temp.Length - 1] % 2 == 0) modulo *= -1; 
    } 
    else if ((intv - modulo) < modulo) 
     modulo = (intv - modulo); 
    else 
     modulo *= -1; 

    return number + modulo; 
} 
+0

awesome ... fanks ... me salvó algo de tiempo trabajando en eso. – Jon

+0

posiblemente encuentre un error. Si ingresa: número = 0.5 intervalo de redondeo 1.0 las cosas van un poco mal con el bit de var var array. – Jon

+0

Eso es cierto, pero un intervalo mayor que el número no tiene ningún sentido, ¿o sí? Agregué un cheque adicional en el método. –

2

Los ejemplos usando la fundición decimal proporcionada en respuesta Jimmy 's no responder a la pregunta, ya que no muestran cómo redondear un valor doble para cualquier valor de precisión de redondeo conforme a lo solicitado. Creo que la respuesta correcta mediante fundición decimal es el siguiente:

public static double RoundI(double number, double roundingInterval) 
    { 
     return (double)((decimal)roundingInterval * Math.Round((decimal)number/(decimal)roundingInterval, MidpointRounding.AwayFromZero)); 
    } 

Debido a que utiliza la fundición decimal, esta solución está sujeta a los errores de conversión mencionados por Jeppe Stig Nielsen en su comentario a la respuesta Jimmy 's .

Además, tenga en cuenta que especifiqué MidpointRounding.AwayFromZero, ya que esto es coherente con la especificación del solicitante que RoundI (1.2345, 0.001) debería dar 1.235.