Одне з найкращих рішень для пошуку кількості цифр після десяткової коми показано в публікації burning_LEGION .
Тут я використовую частини статті на форумі STSdb: Кількість цифр після десяткової коми .
У MSDN ми можемо прочитати таке пояснення:
"Десяткове число - це значення з плаваючою точкою, яке складається із знака, числового значення, де кожна цифра у значенні коливається від 0 до 9, та масштабного коефіцієнта, що вказує на положення плаваючої десяткової коми, що розділяє інтеграл і дробове число частини числового значення. "
А також:
"Двійкове представлення десяткового значення складається з 1-бітового знака, 96-бітового цілого числа та масштабного коефіцієнта, що використовується для розділення 96-бітового цілого числа та вказівки, яка його частина є десятковою часткою. неявно число 10, підняте до показника в межах від 0 до 28. "
На внутрішньому рівні десяткове значення представлене чотирма цілими значеннями.
Існує загальнодоступна функція GetBits для отримання внутрішнього представлення. Функція повертає масив int []:
[__DynamicallyInvokable]
public static int[] GetBits(decimal d)
{
return new int[] { d.lo, d.mid, d.hi, d.flags };
}
Четвертий елемент поверненого масиву містить масштабний коефіцієнт і знак. І як каже MSDN, коефіцієнтом масштабування є неявно число 10, підняте до показника від 0 до 28. Це саме те, що нам потрібно.
Таким чином, на основі всіх вищезазначених досліджень ми можемо побудувати наш метод:
private const int SIGN_MASK = ~Int32.MinValue;
public static int GetDigits4(decimal value)
{
return (Decimal.GetBits(value)[3] & SIGN_MASK) >> 16;
}
Тут SIGN_MASK використовується, щоб ігнорувати знак. Після логічного, і ми також змістили результат на 16 біт вправо, щоб отримати фактичний коефіцієнт масштабування. Це значення, нарешті, вказує кількість цифр після десяткової коми.
Зверніть увагу, що тут MSDN також говорить, що коефіцієнт масштабування також зберігає будь-які кінцеві нулі в десятковому числі. Кінцеві нулі не впливають на значення десяткового числа в арифметичних або операціях порівняння. Однак кінцеві нулі можуть бути виявлені методом ToString, якщо застосовано відповідний рядок формату.
Це рішення виглядає як найкраще, але зачекайте, є й більше. Звертаючись до приватних методів у C #, ми можемо використовувати вирази для побудови прямого доступу до поля прапорів і уникати побудови масиву 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);
}
}
Цей скомпільований код присвоюється полю GetDigits. Зверніть увагу, що функція отримує десяткове значення як ref, тому фактичне копіювання не виконується - лише посилання на значення. Використовувати функцію GetDigits із DecimalHelper просто:
decimal value = 3.14159m;
int digits = DecimalHelper.Instance.GetDigits(ref value);
Це найшвидший можливий метод отримання числа цифр після десяткової коми для десяткових значень.