Existe uma maneira de obter os "números significativos" de um decimal?
OK, depois de alguma investigação, e em grande parte graças às respostas úteis fornecidas por Jon e Hans, foi isso que consegui reunir. Até agora, acho que parece funcionar bem. Eu não apostaria minha vida em sua total correção, é claro.
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;
}
}
Eu não testei tudo isso completamente. Aqui estão algumas entradas / saídas de amostra, no entanto:
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 esse código, imagino que atingiria meu objetivo fazendo algo assim:
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);
}
Ainda não terminei de trabalhar nisso. Quem quiser compartilhar pensamentos: isso seria muito apreciado!
Pergunta originalSuponha que eu escrevi este código:
decimal a = 1.23M;
decimal b = 1.23000M;
Console.WriteLine(a);
Console.WriteLine(b);
O exemplo acima será exibido:
1.23 1.23000
Acho que isso também funciona se eu usardecimal.Parse("1.23")
paraa
edecimal.Parse("1.23000")
parab
(o que significa que esta pergunta se aplica aos casos em que o programa recebe entrada do usuário).
Tão claramentedecimal
O valor está de alguma forma "ciente" do que chamarei deprecisão. No entanto, não vejo membros nodecimal
tipo que fornece qualquer maneira de acessar isso, além deToString
em si.
Suponha que eu quisesse multiplicar doisdecimal
valores e apara o resultado com a precisão do argumento menos preciso. Em outras palavras:
decimal a = 123.4M;
decimal b = 5.6789M;
decimal x = a / b;
Console.WriteLine(x);
As saídas acima:
21.729560302171195125816619416
O que estou perguntando é: como eu poderia escrever um método que retornaria21.73
em vez disso (desde123.4M
tem quatro números significativos)?
Para ser claro: eu sei que poderia ligarToString
nos dois argumentos, conte os números significativos em cada sequência e use esse número para arredondar o resultado do cálculo. eu estou procurando umadiferente maneira, se possível.
(EUAlém disso perceba que na maioria dos cenários em que você está lidando com números significativos, provavelmente não precisa usar odecimal
tipo. Mas estou perguntando porque, como mencionei no começo, odecimal
tipoaparece incluir informações sobre precisão, enquantodouble
até onde eu sei.)