2010-09-10 6 views
15

Actualización

Bien, después de algunas investigaciones, y gracias en gran parte a las útiles respuestas proporcionadas por Jon y Hans, esto es lo que pude juntar. Hasta ahora, creo que parece funcionar bien. No apostaría mi vida por su total corrección, por supuesto.¿Hay alguna forma de obtener las "cifras significativas" de un decimal?

public static int GetSignificantDigitCount(this decimal value) 
{ 
    /* So, the decimal type is basically represented as a fraction of two 
    * integers: a numerator that can be anything, and a denominator that is 
    * some power of 10. 
    * 
    * For example, the following numbers are represented by 
    * the corresponding fractions: 
    * 
    * VALUE NUMERATOR DENOMINATOR 
    * 1  1   1 
    * 1.0  10   10 
    * 1.012 1012  1000 
    * 0.04  4   100 
    * 12.01 1201  100 
    * 
    * So basically, if the magnitude is greater than or equal to one, 
    * the number of digits is the number of digits in the numerator. 
    * If it's less than one, the number of digits is the number of digits 
    * in the denominator. 
    */ 

    int[] bits = decimal.GetBits(value); 

    if (value >= 1M || value <= -1M) 
    { 
     int highPart = bits[2]; 
     int middlePart = bits[1]; 
     int lowPart = bits[0]; 

     decimal num = new decimal(lowPart, middlePart, highPart, false, 0); 

     int exponent = (int)Math.Ceiling(Math.Log10((double)num)); 

     return exponent; 
    } 
    else 
    { 
     int scalePart = bits[3]; 

     // Accoring to MSDN, the exponent is represented by 
     // bits 16-23 (the 2nd word): 
     // http://msdn.microsoft.com/en-us/library/system.decimal.getbits.aspx 
     int exponent = (scalePart & 0x00FF0000) >> 16; 

     return exponent + 1; 
    } 
} 

No lo he probado a fondo. Aquí hay algunas entradas/salidas de la muestra, sin embargo:

 
Value   Precision 
0    1 digit(s). 
0.000   4 digit(s). 
1.23   3 digit(s). 
12.324   5 digit(s). 
1.2300   5 digit(s). 
-5    1 digit(s). 
-5.01   3 digit(s). 
-0.012   4 digit(s). 
-0.100   4 digit(s). 
0.0   2 digit(s). 
10443.31  7 digit(s). 
-130.340  6 digit(s). 
-80.8000  6 digit(s). 

Usando este código, imagino yo lograr mi objetivo de hacer algo como esto:

public static decimal DivideUsingLesserPrecision(decimal x, decimal y) 
{ 
    int xDigitCount = x.GetSignificantDigitCount(); 
    int yDigitCount = y.GetSignificantDigitCount(); 

    int lesserPrecision = System.Math.Min(xDigitCount, yDigitCount); 

    return System.Math.Round(x/y, lesserPrecision); 
} 

realmente no he terminado de trabajar a través esto, sin embargo. Cualquiera que quiera compartir pensamientos: ¡sería muy apreciado!


pregunta original

Supongamos que tengo escribir este código:

decimal a = 1.23M; 
decimal b = 1.23000M; 

Console.WriteLine(a); 
Console.WriteLine(b); 

Lo anterior es la salida:

 
1.23 
1.23000 

Me parece que esto también funciona si uso decimal.Parse("1.23") de a y decimal.Parse("1.23000") para b (whi ch significa que esta pregunta se aplica a los casos en que el programa recibe la entrada del usuario).

Así que claramente un valor de decimal es de alguna manera "consciente" de lo que llamaré su precisión. Sin embargo, no veo miembros en el tipo decimal que proporcionen ninguna forma de acceder a esto, aparte del ToString.

Supongamos que quiero multiplicar dos valores de decimal y recortar el resultado con la precisión del argumento menos preciso. En otras palabras:

decimal a = 123.4M; 
decimal b = 5.6789M; 

decimal x = a/b; 

Console.WriteLine(x); 

Las salidas anteriores:

 
21.729560302171195125816619416 

Lo que estoy preguntando es: ¿cómo podría escribir un método que devolvería 21.73 lugar (ya que 123.4M tiene cuatro cifras significativas)?

Para ser claros: me doy cuenta de que podría llamar a ToString en ambos argumentos, contar las cifras significativas en cada cadena, y usar este número para redondear el resultado del cálculo. Estoy buscando manera diferente, si es posible.

(tambiéndan cuenta de que en la mayoría de escenarios en los que está tratando con cifras significativas, es probable que no necesita utilizar el tipo decimal. Pero Lo digo porque, como he mencionado al principio, el decimal tipo aparece para incluir información acerca de la precisión, mientras que double no, hasta donde yo sé.)

+2

¡No sabía que quería saber esto hasta que me lo pidiera +1! – msarchet

+3

Su función no funciona correctamente para algunas de esas entradas. Por ejemplo, -0.012 tiene solo 2 dígitos significativos, no 4. –

+0

@JamesJones Claramente se está refiriendo a un concepto diferente de dígitos significativos, no que uno aprendería en un curso de matemáticas. Tal vez el nombre "dígitos usados" tendría más sentido. – ErikE

Respuesta

3

Puede usar Decimal.GetBits para obtener los datos brutos, y resuélvalos de eso.

Desafortunadamente no tengo tiempo para escribir el código de muestra en este momento, y es probable que desee utilizar BigInteger para algo de la manipulación, si está utilizando .NET 4, pero espero que esto lo haga funcionar. . Solo calcular la precisión y luego llamar al Math.Round en el resultado original puede ser un buen comienzo.

+0

Estoy aceptando esta respuesta a pesar de que no he terminado de encontrar mi solución, porque definitivamente me da la información que necesito. (Tristemente, esperaba algo un poco más accesible que 'GetBits', pero parece que es lo mejor con lo que tenemos que trabajar!) –

3

Sí, a diferencia de los tipos de punto flotante, System.Decimal realiza un seguimiento del número de dígitos en el literal. Esta es una característica de decimal.Parse(), ya sea ejecutada por su código usted mismo o por el compilador cuando analiza un literal en su programa. Puede recuperar esta información, consulte el código en mi respuesta en this thread.

Recuperar el número de dígitos significativos después de hacer las matemáticas en el valor me parece una posibilidad remota. No tengo idea si los operadores los conservan, por favor, háganos saber lo que averigua.

+3

' decimal' * es * un tipo de coma flotante :) –

+0

Sí, soy bonita Asegúrese de que la información no se preserve. Lo cual tiene sentido, quiero decir, considerando algo así como '1M/4M', obviamente debería salir a' 0.25M' no a '0.3M'. De todos modos, abordé el problema un poco y se me ocurrió algo parecido a una solución en proceso (¡con el mismo truco que veo que sugeriste en la respuesta vinculada!) ... mi código es muy feo , aunque. Si obtiene un minuto gratis más tarde, definitivamente apreciaría sus pensamientos sobre mi actualización en la parte superior de la pregunta. –

0

La solución anterior se cae de la cama rápidamente con valores como 1200 donde el número de dígitos significativos es 2 pero la función retorna 4. Quizás haya una manera consistente de tratar con una situación como la comprobación para asegurarse de que el valor de retorno no incluir ceros finales en la parte del número entero sin una parte fraccionaria.

Cuestiones relacionadas