在burning_LEGION的post中显示了找到小数点后数字位数的最佳解决方案之一。
在这里,我使用的是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#中的私有方法,我们可以使用表达式来构建对flags字段的直接访问,并避免构造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接收,因此不执行任何实际复制-仅引用该值。使用DecimalHelper中的GetDigits函数很容易:
decimal value = 3.14159m;
int digits = DecimalHelper.Instance.GetDigits(ref value);
这是获取十进制值的小数点后位数的最快方法。