无论文化如何,查找十进制值的小数位数

我想知道是否有一个简洁和准确的方法来拉出十进制数的小数位数(作为一个整数),这将是安全使用跨不同的文化信息?

例如:
19.0应该返回1,
27.5999应该返回4,
19.12应该返回2,
等等

我写了一个查询,做一个string拆分在一段时间来发现小数位:

int priceDecimalPlaces = price.ToString().Split('.').Count() > 1 ? price.ToString().Split('.').ToList().ElementAt(1).Length : 0; 

但是,对我来说,这只会在使用'。'的地区才起作用。 作为小数分隔符,因此在不同系统中非常脆弱。

我用乔的方式来解决这个问题:)

 decimal argument = 123.456m; int count = BitConverter.GetBytes(decimal.GetBits(argument)[3])[2]; 

我可能会在@ fixagon的答案中使用该解决scheme。

但是,Decimal结构没有获取小数的方法,您可以调用Decimal.GetBits来提取二进制表示,然后使用整数值和小数计算小数位数。

这可能比格式化string更快,但是您必须处理大量的小数以注意区别。

我将把练习作为一个练习。

由于没有提供的答案是足够好的魔术数字“-0.01f”转换为十进制..即: GetDecimal((decimal)-0.01f);
我只能假设一个巨大的心灵屁病毒3年前攻击大家:)
这个邪恶和可怕的问题似乎是一个可行的实施,在这个点之后,计算小数点的非常复杂的问题 – 没有任何string,没有文化,不需要数位,也不需要阅读math论坛。只是简单的三年级math。

 public static class MathDecimals { public static int GetDecimalPlaces(decimal n) { n = Math.Abs(n); //make sure it is positive. n -= (int)n; //remove the integer part of the number. var decimalPlaces = 0; while (n > 0) { decimalPlaces++; n *= 10; n -= (int)n; } return decimalPlaces; } } 

 private static void Main(string[] args) { Console.WriteLine(1/3m); //this is 0.3333333333333333333333333333 Console.WriteLine(1/3f); //this is 0.3333333 Console.WriteLine(MathDecimals.GetDecimalPlaces(0.0m)); //0 Console.WriteLine(MathDecimals.GetDecimalPlaces(1/3m)); //28 Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)(1 / 3f))); //7 Console.WriteLine(MathDecimals.GetDecimalPlaces(-1.123m)); //3 Console.WriteLine(MathDecimals.GetDecimalPlaces(43.12345m)); //5 Console.WriteLine(MathDecimals.GetDecimalPlaces(0)); //0 Console.WriteLine(MathDecimals.GetDecimalPlaces(0.01m)); //2 Console.WriteLine(MathDecimals.GetDecimalPlaces(-0.001m)); //3 Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)-0.00000001f)); //8 Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)0.0001234f)); //7 Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)0.01f)); //2 Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)-0.01f)); //2 } 

在burning_LEGION的文章中显示了查找小数点后数字位数的最佳解决scheme之一。

在这里,我使用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的指数。这正是我们所需要的。

因此,基于以上所有调查,我们可以构build我们的方法:

 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也表示,比例因子也保留了任何一个十进制数的尾随零。 在算术或比较操作中,尾部零不影响十进制数的值。 但是,如果应用了适当的格式string,则ToString方法可能会显示尾随零。

这个解决scheme看起来是最好的,但是等等,还有更多。 通过访问C#中的私有方法,我们可以使用expression式构build对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字段。 请注意,该函数接收的十进制值作为参考,所以没有进行实际的复制 – 只有一个参考值。 使用DecimalHelper的GetDigits函数很简单:

 decimal value = 3.14159m; int digits = DecimalHelper.Instance.GetDigits(ref value); 

这是获取小数点后小数点后数字位数最快的方法。

你可以使用InvariantCulture

 string priceSameInAllCultures = price.ToString(System.Globalization.CultureInfo.InvariantCulture); 

另一种可能性是做这样的事情:

 private int GetDecimals(decimal d, int i = 0) { decimal multiplied = (decimal)((double)d * Math.Pow(10, i)); if (Math.Round(multiplied) == multiplied) return i; return GetDecimals(d, i+1); } 

还有另一种方法,使用SqlDecimaltypes,它具有scale属性和小数点右边的数字。 将您的十进制值转换为SqlDecimal,然后访问Scale。

 ((SqlDecimal)(decimal)yourValue).Scale 

这里的大多数人似乎并不知道小数将尾随零视为存储和打印的重要部分。

因此,0.1m,0.10m和0.100m可以比较相等,它们的存储方式不同(分别为数值/比例1/1,10/2和100/3),分别打印为0.1,0.10和0.100 ,通过ToString()

因此,报告“精度太高”的解决scheme实际上是按照decimal报告正确的精度。

另外,基于math的解决scheme(如乘以10的幂)可能会非常慢(小数是算术的两倍慢40倍,并且您不希望混合浮点,因为这可能会导致不精确)。 类似的,铸造为int或者作为截断的一种手段是很容易出错的( decimal范围远远大于其中的任何一个 – 它基于一个96位整数)。

虽然不是那么优雅,但以下几点可能是获得精确度的最快方法之一(当定义为“不包括尾随零的小数位数”时):

 public static int PrecisionOf(decimal d) { var text = d.ToString(System.Globalization.CultureInfo.InvariantCulture).TrimEnd('0'); var decpoint = text.IndexOf('.'); if (decpoint < 0) return 0; return text.Length - decpoint - 1; } 

不变的文化保证了'。' 作为小数点,尾随零被修整,然后它只是在小数点后面有多less位置(如果有的话)。

编辑:改变返回types为int

依靠小数的内部表示并不酷。

这个怎么样:

  int CountDecimalDigits(decimal n) { return n.ToString(System.Globalization.CultureInfo.InvariantCulture) //.TrimEnd('0') uncomment if you don't want to count trailing zeroes .SkipWhile(c => c != '.') .Skip(1) .Count(); } 

我昨天写了一个简洁的小方法,它也返回小数位数,而不必依赖任何string分裂或文化,这是理想的:

 public int GetDecimalPlaces(decimal decimalNumber) { // try { // PRESERVE:BEGIN int decimalPlaces = 1; decimal powers = 10.0m; if (decimalNumber > 0.0m) { while ((decimalNumber * powers) % 1 != 0.0m) { powers *= 10.0m; ++decimalPlaces; } } return decimalPlaces; 

你可以试试:

 int priceDecimalPlaces = price.ToString(System.Globalization.CultureInfo.InvariantCulture) .Split('.')[1].Length; 

我在我的代码中使用以下机制

  public static int GetDecimalLength(string tempValue) { int decimalLength = 0; if (tempValue.Contains('.') || tempValue.Contains(',')) { char[] separator = new char[] { '.', ',' }; string[] tempstring = tempValue.Split(separator); decimalLength = tempstring[1].Length; } return decimalLength; } 

十进制input= 3.376; var instring = input.ToString();

调用GetDecimalLength(instring)

我build议使用这种方法:

  public static int GetNumberOfDecimalPlaces(decimal value, int maxNumber) { if (maxNumber == 0) return 0; if (maxNumber > 28) maxNumber = 28; bool isEqual = false; int placeCount = maxNumber; while (placeCount > 0) { decimal vl = Math.Round(value, placeCount - 1); decimal vh = Math.Round(value, placeCount); isEqual = (vl == vh); if (isEqual == false) break; placeCount--; } return Math.Min(placeCount, maxNumber); }