Jedno z najlepszych rozwiązań w celu znalezienia liczby cyfr po przecinku jest pokazane w poście burn_LEGION .
Tutaj używam części z artykułu na forum STSdb: Liczba cyfr po przecinku .
W MSDN możemy przeczytać następujące wyjaśnienie:
„Liczba dziesiętna to wartość zmiennoprzecinkowa, która składa się ze znaku, wartości liczbowej, w której każda cyfra w wartości mieści się w zakresie od 0 do 9, oraz współczynnika skalowania, który wskazuje pozycję zmiennoprzecinkowego przecinka, który oddziela całkę i ułamek części wartości liczbowej. "
I również:
„Binarna reprezentacja wartości Decimal składa się ze znaku 1-bitowego, 96-bitowej liczby całkowitej i współczynnika skalowania używanego do dzielenia 96-bitowej liczby całkowitej i określania, która jej część jest ułamkiem dziesiętnym. Współczynnik skalowania to domyślnie liczba 10, podniesiona do wykładnika z zakresu od 0 do 28. ”
Na poziomie wewnętrznym wartość dziesiętna jest reprezentowana przez cztery wartości całkowite.
Istnieje publicznie dostępna funkcja GetBits do uzyskiwania reprezentacji wewnętrznej. Funkcja zwraca tablicę int []:
[__DynamicallyInvokable]
public static int[] GetBits(decimal d)
{
return new int[] { d.lo, d.mid, d.hi, d.flags };
}
Czwarty element zwróconej tablicy zawiera współczynnik skali i znak. Jak podaje MSDN, współczynnik skalowania jest niejawnie liczbą 10, podniesioną do wykładnika z zakresu od 0 do 28. Dokładnie tego potrzebujemy.
Zatem na podstawie wszystkich powyższych badań możemy skonstruować naszą metodę:
private const int SIGN_MASK = ~Int32.MinValue;
public static int GetDigits4(decimal value)
{
return (Decimal.GetBits(value)[3] & SIGN_MASK) >> 16;
}
Tutaj SIGN_MASK jest używany do ignorowania znaku. Po logicznym i przesunęliśmy również wynik o 16 bitów w prawo, aby otrzymać rzeczywisty współczynnik skali. Ostatecznie ta wartość wskazuje liczbę cyfr po przecinku.
Zauważ, że tutaj MSDN mówi również, że współczynnik skalowania zachowuje również wszelkie końcowe zera w liczbie dziesiętnej. Końcowe zera nie wpływają na wartość liczby Decimal w operacjach arytmetycznych lub porównawczych. Jednak końcowe zera mogą zostać ujawnione przez metodę ToString, jeśli zostanie zastosowany odpowiedni ciąg formatu.
To rozwiązanie wygląda jak najlepsze, ale czekaj, jest więcej. Uzyskując dostęp do metod prywatnych w C # , możemy użyć wyrażeń do zbudowania bezpośredniego dostępu do pola flag i uniknięcia konstruowania tablicy 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);
}
}
Ten skompilowany kod jest przypisany do pola GetDigits. Zauważ, że funkcja otrzymuje wartość dziesiętną jako ref, więc nie jest wykonywane żadne kopiowanie - tylko odniesienie do wartości. Korzystanie z funkcji GetDigits z DecimalHelper jest łatwe:
decimal value = 3.14159m;
int digits = DecimalHelper.Instance.GetDigits(ref value);
Jest to najszybsza możliwa metoda uzyskania liczby cyfr po przecinku dla wartości dziesiętnych.