C# 无论文化如何,查找十进制值中的小数位数
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/13477689/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Find number of decimal places in decimal value regardless of culture
提问by Jesse Carter
I'm wondering if there is a concise and accurate way to pull out the number of decimal places in a decimal value (as an int) that will be safe to use across different culture info?
我想知道是否有一种简洁准确的方法来提取十进制值(作为整数)中的小数位数,可以安全地跨不同的文化信息使用?
For example:
19.0 should return 1,
27.5999 should return 4,
19.12 should return 2,
etc.
例如:
19.0 应该返回
1,27.5999 应该返回 4,19.12
应该返回 2,
等等。
I wrote a query that did a string split on a period to find decimal places:
我写了一个查询,在一个句点上进行字符串拆分以查找小数位:
int priceDecimalPlaces = price.ToString().Split('.').Count() > 1
? price.ToString().Split('.').ToList().ElementAt(1).Length
: 0;
But it occurs to me that this will only work in regions that use the '.' as a decimal separator and is therefore very brittle across different systems.
但我突然想到,这只适用于使用“.”的区域。作为小数点分隔符,因此在不同系统中非常脆弱。
采纳答案by burning_LEGION
回答by fixagon
you can use the InvariantCulture
你可以使用 InvariantCulture
string priceSameInAllCultures = price.ToString(System.Globalization.CultureInfo.InvariantCulture);
another possibility would be to do something like that:
另一种可能性是做这样的事情:
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);
}
回答by NicoRiff
You can try:
你可以试试:
int priceDecimalPlaces =
price.ToString(System.Globalization.CultureInfo.InvariantCulture)
.Split('.')[1].Length;
回答by Joe
I'd probably use the solution in @fixagon's answer.
我可能会使用@fixagon's answer 中的解决方案。
However, while the Decimal struct doesn't have a method to get the number of decimals, you could call Decimal.GetBitsto extract the binary representation, then use the integer value and scale to compute the number of decimals.
然而,虽然 Decimal 结构没有获取小数位数的方法,您可以调用Decimal.GetBits来提取二进制表示,然后使用整数值和小数位数来计算小数位数。
This would probably be faster than formatting as a string, though you'd have to be processing an awful lot of decimals to notice the difference.
这可能比格式化为字符串更快,尽管您必须处理大量小数才能注意到差异。
I'll leave the implementation as an exercise.
我将把实现作为练习。
回答by Jesse Carter
I wrote a concise little method yesterday that also returns the number of decimal places without having to rely on any string splits or cultures which is ideal:
我昨天写了一个简洁的小方法,它也返回小数位数,而不必依赖任何字符串拆分或文化,这是理想的:
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;
回答by Srikanth
I use the following mechanism in my code
我在我的代码中使用以下机制
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;
}
decimal input=3.376; var instring=input.ToString();
十进制输入=3.376;var instring=input.ToString();
call GetDecimalLength(instring)
调用 GetDecimalLength(instring)
回答by Kris
One of the best solutions for finding the number of digits after the decimal point is shown in burning_LEGION's post.
寻找小数点后位数的最佳解决方案之一显示在burning_LEGION 的帖子中。
Here I am using parts from a STSdb forum article: Number of digits after decimal point.
在这里,我使用了 STSdb 论坛文章中的部分:Number ofnumbers after decimal point。
In MSDN we can read the following explanation:
在MSDN中我们可以阅读以下解释:
"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."
“十进制数是一个浮点值,它由一个符号、一个数值(该值中的每个数字的范围为 0 到 9)和一个表示分隔整数和小数的浮点小数点位置的比例因子组成数值的一部分。”
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."
“Decimal 值的二进制表示由 1 位符号、96 位整数和用于划分 96 位整数并指定它的哪一部分是小数的比例因子组成。比例因子是隐含数字 10,升至 0 到 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:
有一个公开可用的 GetBits 函数来获取内部表示。该函数返回一个 int[] 数组:
[__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.
返回数组的第四个元素包含一个比例因子和一个符号。正如 MSDN 所说,缩放因子隐含地是数字 10,上升到 0 到 28 之间的指数。这正是我们所需要的。
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.
这里 SIGN_MASK 用于忽略符号。在逻辑之后,我们还将结果右移 16 位以接收实际比例因子。最后,该值表示小数点后的位数。
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.
请注意,此处 MSDN 还说缩放因子还保留了十进制数中的任何尾随零。尾随零不会影响算术或比较运算中十进制数的值。但是,如果应用了适当的格式字符串,ToString 方法可能会显示尾随零。
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:
这个解决方案看起来是最好的,但是等等,还有更多。通过访问 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);
}
}
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:
此编译代码分配给 GetDigits 字段。请注意,该函数接收十进制值作为 ref,因此不会执行实际复制 - 仅是对该值的引用。使用 DecimalHelper 中的 GetDigits 函数很容易:
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.
这是获取十进制值小数点后位数的最快方法。
回答by G.Y
Since none of the answers supplied were good enough for the magic number "-0.01f" converted to decimal.. i.e: GetDecimal((decimal)-0.01f);
I can only assume a colossal mind-fart virus attacked everyone 3 years ago :)
Here is what seems to be a working implementation to this evil and monstrous problem, the very complicated problem of counting the decimal places after the point - no strings, no cultures, no need to count the bits and no need to read math forums.. just simple 3rd grade math.
由于提供的答案都不足以将幻数“-0.01f”转换为十进制..即:GetDecimal((decimal)-0.01f);
我只能假设 3 年前一个巨大的脑放屁病毒攻击了所有人:)
这似乎是一个有效的方法实现这个邪恶而可怕的问题,计算点后小数位的非常复杂的问题 - 没有字符串,没有文化,不需要计算位,不需要阅读数学论坛......只是简单的三年级数学。
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
}
回答by Zastai
Most people here seem to be unaware that decimal considers trailing zeroes as significant for storage and printing.
这里的大多数人似乎不知道十进制认为尾随零对于存储和打印很重要。
So 0.1m, 0.10m and 0.100m may compare as equal, they are stored differently (as value/scale 1/1, 10/2 and 100/3, respectively), and will be printed as 0.1, 0.10 and 0.100, respectively, by ToString().
所以 0.1m、0.10m 和 0.100m 可能比较相等,它们的存储方式不同(分别作为值/比例 1/1、10/2 和 100/3),并将分别打印为 0.1、0.10 和 0.100 ,由ToString()。
As such, the solutions that report "too high a precision" are actually reporting the correctprecision, on decimal's terms.
因此,报告“精度太高”的解决方案实际上报告了正确的精度,根据decimal的条款。
In addition, math-based solutions (like multiplying by powers of 10) will likely be very slow (decimal is ~40x slower than double for arithmetic, and you don't want to mix in floating-point either because that's likely to introduce imprecision). Similarly, casting to intor longas a means of truncating is error-prone (decimalhas a much greater range than either of those - it's based around a 96-bit integer).
此外,基于数学的解决方案(如乘以 10 的幂)可能会非常慢(十进制比算术的 double 慢约 40 倍,并且您也不想混入浮点数,因为这可能会引入不精确性)。类似地,强制转换为int或long作为截断的方式容易出错(decimal其范围比其中任何一个都大得多 - 它基于 96 位整数)。
While not elegant as such, the following will likely be one of the fastest way to get the precision (when defined as "decimal places excluding trailing zeroes"):
虽然不优雅,但以下可能是获得精度的最快方法之一(当定义为“不包括尾随零的小数位”时):
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;
}
The invariant culture guarantees a '.' as decimal point, trailing zeroes are trimmed, and then it's just a matter of seeing of how many positions remain after the decimal point (if there even is one).
不变的文化保证'.' 作为小数点,尾随零被修剪,然后只是看看小数点后还有多少位置(如果有的话)。
Edit: changed return type to int
编辑:将返回类型更改为 int
回答by Clement
Relying on the internal representation of decimals is not cool.
依靠小数的内部表示并不酷。
How about this:
这个怎么样:
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();
}

