Una delle migliori soluzioni per trovare il numero di cifre dopo il punto decimale è mostrata nel post di burning_LEGION .
Qui sto usando parti di un articolo del forum STSdb: Numero di cifre dopo il punto decimale .
In MSDN possiamo leggere la seguente spiegazione:
"Un numero decimale è un valore a virgola mobile che consiste in un segno, un valore numerico in cui ogni cifra del valore è compresa tra 0 e 9 e un fattore di scala che indica la posizione di un punto decimale mobile che separa l'integrale e la frazione parti del valore numerico. "
E anche:
"La rappresentazione binaria di un valore Decimal è costituita da un segno a 1 bit, un numero intero a 96 bit e un fattore di scala utilizzato per dividere l'intero a 96 bit e specificare quale parte di esso è una frazione decimale. Il fattore di scala è implicitamente il numero 10, elevato a un esponente compreso tra 0 e 28 ".
A livello interno il valore decimale è rappresentato da quattro valori interi.
Esiste una funzione GetBits disponibile pubblicamente per ottenere la rappresentazione interna. La funzione restituisce un array int []:
[__DynamicallyInvokable]
public static int[] GetBits(decimal d)
{
return new int[] { d.lo, d.mid, d.hi, d.flags };
}
Il quarto elemento della matrice restituita contiene un fattore di scala e un segno. E come dice l'MSDN, il fattore di scala è implicitamente il numero 10, elevato a un esponente compreso tra 0 e 28. Questo è esattamente ciò di cui abbiamo bisogno.
Quindi, sulla base di tutte le indagini di cui sopra, possiamo costruire il nostro metodo:
private const int SIGN_MASK = ~Int32.MinValue;
public static int GetDigits4(decimal value)
{
return (Decimal.GetBits(value)[3] & SIGN_MASK) >> 16;
}
Qui un SIGN_MASK viene utilizzato per ignorare il segno. Dopo logico e abbiamo anche spostato il risultato con 16 bit a destra per ricevere il fattore di scala effettivo. Questo valore, infine, indica il numero di cifre dopo il punto decimale.
Notare che qui MSDN dice anche che il fattore di scala preserva anche gli zeri finali in un numero decimale. Gli zeri finali non influiscono sul valore di un numero Decimal nelle operazioni aritmetiche o di confronto. Tuttavia, gli zeri finali potrebbero essere rilevati dal metodo ToString se viene applicata una stringa di formato appropriata.
Questa soluzione sembra la migliore, ma aspetta, c'è di più. Con l'accesso a metodi privati in C # possiamo usare espressioni per costruire un accesso diretto al campo di bandiere e di evitare la costruzione della matrice 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);
}
}
Questo codice compilato viene assegnato al campo GetDigits. Si noti che la funzione riceve il valore decimale come ref, quindi non viene eseguita alcuna copia effettiva, solo un riferimento al valore. Usare la funzione GetDigits da DecimalHelper è facile:
decimal value = 3.14159m;
int digits = DecimalHelper.Instance.GetDigits(ref value);
Questo è il metodo più veloce possibile per ottenere il numero di cifre dopo il punto decimale per i valori decimali.