One of the best solutions for finding the number of digits after the decimal point is shown in burning_LEGION's post.
Here I am using parts from a STSdb forum article: Number of digits after decimal point.
In MSDN we can read the following explanation:
"A decimal number is a floating-point value that consists of a sign, a numeric value where each digit in the value ranges from 0 to 9,
and a scaling factor that indicates the position of a floating decimal point that separates the integral and fractional parts of the numeric value."
And also:
"The binary representation of a Decimal value consists of a 1-bit sign, a 96-bit integer number, and a scaling factor used to divide the 96-bit integer
and specify what portion of it is a decimal fraction. The scaling factor is implicitly the number 10, raised to an exponent ranging from 0 to 28."
On internal level the decimal value is represented by four integer values.
There is a publicly available GetBits function for getting the internal representation. The function returns an int[] array:
[__DynamicallyInvokable]
public static int[] GetBits(decimal d)
{
return new int[] { d.lo, d.mid, d.hi, d.flags };
}
The fourth element of the returned array contains a scale factor and a sign. And as the MSDN says the scaling factor is implicitly the number 10, raised to an exponent ranging from 0 to 28. This is exactly what we need.
Thus, based on all above investigations we can construct our method:
private const int SIGN_MASK = ~Int32.MinValue;
public static int GetDigits4(decimal value)
{
return (Decimal.GetBits(value)[3] & SIGN_MASK) >> 16;
}
Here a SIGN_MASK is used to ignore the sign. After logical and we have also shifted the result with 16 bits to the right to receive the actual scale factor. This value, finally, indicates the number of digits after the decimal point.
Note that here MSDN also says the scaling factor also preserves any trailing zeros in a Decimal number. Trailing zeros do not affect the value of a Decimal number in arithmetic or comparison operations. However, trailing zeros might be revealed by the ToString method if an appropriate format string is applied.
This solutions looks like the best one, but wait, there is more. By accessing private methods in C# we can use expressions to build a direct access to the flags field and avoid constructing the int array:
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 (value.flags & ~Int32.MinValue) >> 16
return Expression.Lambda<GetDigitsDelegate>(digits, value);
}
}
This compiled code is assigned to the GetDigits field. Note that the function receives the decimal value as ref, so no actual copying is performed - only a reference to the value. Using the GetDigits function from the DecimalHelper is easy:
decimal value = 3.14159m;
int digits = DecimalHelper.Instance.GetDigits(ref value);
This is the fastest possible method for getting number of digits after decimal point for decimal values.
19.0
to return1
is an implementation detail regarding the internal storage of the value19.0
. The fact is that it is perfectly legitimate for the program to store this as190×10⁻¹
or1900×10⁻²
or19000×10⁻³
. All of those are equal. The fact that it uses the first representation when given a value of19.0M
and this is exposed when usingToString
without a format specifier is just a coincidence, and a happy-ish thing. Except it's not happy when people rely on the exponent in cases where they shouldn't. – Weiss19M
from19.0M
from19.00M
, you'll need to create a new class that bundles the underlying value as one property and the number of decimal places as another property. – Weisswhatever(654.32100m)
return? – Carrie21.40
would also helpfully eviscerate any possibility of confusion, IMHO. – Chasse