Uma das melhores soluções para encontrar o número de dígitos após a vírgula decimal é mostrada na postagem de burn_LEGION .
Aqui estou usando partes de um artigo do fórum STSdb: Número de dígitos após a vírgula decimal .
No MSDN podemos ler a seguinte explicação:
"Um número decimal é um valor de ponto flutuante que consiste em um sinal, um valor numérico em que cada dígito no valor varia de 0 a 9 e um fator de escala que indica a posição de um ponto decimal flutuante que separa o integral do fracionário partes do valor numérico. "
E também:
"A representação binária de um valor decimal consiste em um sinal de 1 bit, um número inteiro de 96 bits e um fator de escala usado para dividir o inteiro de 96 bits e especificar qual parte dele é uma fração decimal. O fator de escala é implicitamente, o número 10, elevado a um expoente que varia de 0 a 28. "
No nível interno, o valor decimal é representado por quatro valores inteiros.
Há uma função GetBits disponível publicamente para obter a representação interna. A função retorna uma matriz int []:
[__DynamicallyInvokable]
public static int[] GetBits(decimal d)
{
return new int[] { d.lo, d.mid, d.hi, d.flags };
}
O quarto elemento da matriz retornada contém um fator de escala e um sinal. E, como diz o MSDN, o fator de escala é implicitamente o número 10, elevado a um expoente que varia de 0 a 28. Isso é exatamente o que precisamos.
Assim, com base em todas as investigações acima, podemos construir nosso método:
private const int SIGN_MASK = ~Int32.MinValue;
public static int GetDigits4(decimal value)
{
return (Decimal.GetBits(value)[3] & SIGN_MASK) >> 16;
}
Aqui, um SIGN_MASK é usado para ignorar o sinal. Depois de lógico e, também mudamos o resultado com 16 bits à direita para receber o fator de escala real. Este valor, por fim, indica o número de dígitos após a vírgula decimal.
Observe que aqui o MSDN também informa que o fator de escala também preserva quaisquer zeros à direita em um número decimal. Os zeros à direita não afetam o valor de um número decimal em operações aritméticas ou de comparação. No entanto, zeros finais podem ser revelados pelo método ToString se uma seqüência de caracteres de formato apropriada for aplicada.
Esta solução parece ser a melhor, mas espere, tem mais. Ao acessar métodos privados em C # podemos usar as expressões de construir um acesso directo ao campo de bandeiras e evitar construir a matriz int:
public delegate int GetDigitsDelegate(ref Decimal value);
public class DecimalHelper
{
public static readonly DecimalHelper Instance = new DecimalHelper();
public readonly GetDigitsDelegate GetDigits;
public readonly Expression<GetDigitsDelegate> GetDigitsLambda;
public DecimalHelper()
{
GetDigitsLambda = CreateGetDigitsMethod();
GetDigits = GetDigitsLambda.Compile();
}
private Expression<GetDigitsDelegate> CreateGetDigitsMethod()
{
var value = Expression.Parameter(typeof(Decimal).MakeByRefType(), "value");
var digits = Expression.RightShift(
Expression.And(Expression.Field(value, "flags"), Expression.Constant(~Int32.MinValue, typeof(int))),
Expression.Constant(16, typeof(int)));
return Expression.Lambda<GetDigitsDelegate>(digits, value);
}
}
Este código compilado é atribuído ao campo GetDigits. Observe que a função recebe o valor decimal como ref, portanto, nenhuma cópia real é executada - apenas uma referência ao valor. Usar a função GetDigits do DecimalHelper é fácil:
decimal value = 3.14159m;
int digits = DecimalHelper.Instance.GetDigits(ref value);
Este é o método mais rápido possível para obter o número de dígitos após a vírgula decimal para valores decimais.