¿Hay alguna manera de obtener las "cifras significativas" de un decimal?
De acuerdo, después de un poco de investigación, y gracias en gran parte a las útiles respuestas proporcionadas por Jon y Hans, esto es lo que pude reunir. Hasta ahora creo que parece funcionar bien. No apostaría mi vida a su total corrección, por supuesto.
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 tan a fondo. Sin embargo, aquí hay algunas entradas / salidas de muestra:
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 que lograría mi objetivo haciendo 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);
}
Sin embargo, realmente no he terminado de trabajar en esto. Cualquiera que quiera compartir pensamientos: ¡eso sería muy apreciado!
Pregunta originalSupongamos que escribo este código:
decimal a = 1.23M;
decimal b = 1.23000M;
Console.WriteLine(a);
Console.WriteLine(b);
Lo anterior generará:
1.23 1.23000
Me parece que esto también funciona si usodecimal.Parse("1.23")
paraa
ydecimal.Parse("1.23000")
parab
(lo que significa que esta pregunta se aplica a los casos en que el programa recibe información del usuario).
Claramente undecimal
el valor es de alguna manera "consciente" de lo que llamaré suprecisión. Sin embargo, no veo miembros en eldecimal
tipo que proporciona cualquier forma de acceder a esto, aparte deToString
sí mismo.
Supongamos que quisiera multiplicar dosdecimal
valores y recortar el resultado a 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 pregunto es: ¿cómo podría escribir un método que devuelva21.73
en su lugar (desde123.4M
tiene cuatro cifras significativas)?
Para ser claros: me doy cuenta de que podría llamarToString
en ambos argumentos, cuente las cifras significativas en cada cadena y use este número para redondear el resultado del cálculo. Estoy buscandodiferente camino, si es posible.
(YOademás tenga en cuenta que en la mayoría de los escenarios en los que se trata de cifras significativas, es probable que no necesite usar eldecimal
tipo. Pero estoy preguntando porque, como mencioné al principio, eldecimal
tipoaparece para incluir información sobre precisión, mientras quedouble
no, por lo que yo sé).