Burning_LEGION 的帖子中显示了查找小数点后位数的最佳解决方案之一。
在这里,我使用了 STSdb 论坛文章中的部分内容:小数点后的位数。
在 MSDN 中我们可以阅读以下解释:
“十进制数是一个浮点值,它由一个符号、一个数值(值中的每个数字的范围从 0 到 9)和一个比例因子组成,该比例因子表示将整数和小数分开的浮点小数点的位置数值的一部分。”
并且:
“Decimal 值的二进制表示由一个 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 还说比例因子还保留了十进制数中的任何尾随零。尾随零不会影响算术或比较运算中的 Decimal 数的值。但是,如果应用了适当的格式字符串,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 (value.flags & ~Int32.MinValue) >> 16
return Expression.Lambda<GetDigitsDelegate>(digits, value);
}
}
此编译代码分配给 GetDigits 字段。请注意,该函数接收十进制值作为 ref,因此不执行实际复制 - 仅对该值的引用。使用 DecimalHelper 中的 GetDigits 函数很简单:
decimal value = 3.14159m;
int digits = DecimalHelper.Instance.GetDigits(ref value);
这是获取十进制值小数点后位数的最快方法。