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

Jes*_*ter 72 c# cultureinfo decimal

我想知道是否有一种简洁而准确的方法来提取十进制值中的小数位数(作为int),可以安全地在不同的文化信息中使用?

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

我写了一个查询,在一个句点上执行字符串拆分以查找小数位:

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

但我发现这只适用于使用'.'的地区.作为小数分隔符,因此在不同系统中非常脆弱.

bur*_*ION 144

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

decimal argument = 123.456m;
int count = BitConverter.GetBytes(decimal.GetBits(argument)[3])[2];
Run Code Online (Sandbox Code Playgroud)

  • 不确定这个应该是优雅还是优秀的.这就像它得到的混淆一样.谁知道它是否适用于所有情况.不可能确定. (13认同)
  • `decimal`在昏迷后保持计数,这就是为什么你找到这个"问题",你必须将十进制转换为双倍并再次为十进制转换为修复:BitConverter.GetBytes(decimal.GetBits((decimal)(double)参数)[3 ])[2]; (9认同)
  • @Nicholi - 不,这是非常糟糕的**因为该方法依赖于小数**的基础位的放置 - 这种方法有_many ways_来表示_same number_.你不会根据它的私有字段的状态来测试一个类吗? (7认同)
  • 在进一步看了这个并看到它在行动之后我将它标记为答案因为在我看来这是我在这里看到的最简洁和优雅的返回小数位的方法.如果可以,我会再次+1:D (5认同)
  • 这不适合我.从SQL返回的值是21.17,它表示4位数.数据类型被定义为DECIMAL(12,4),所以可能是它(使用实体框架). (3认同)
  • 最近我遇到了这个解决方案的问题。问题是它也计算尾随零。例如对于 var 参数 = 123.4560m; 结果是 4。 (2认同)
  • @raznagul - 对于 123.4560,我建议 4 是正确的结果,因为该数字精确到 4 dp。例如,123.456 到 4dps 的数字可能是 123.4558。 (2认同)
  • 如果问题是"十进制对象中有多少位数",GetBits提供了该解决方案.而且,如果Decimal的底层表示要改变,GetBits的实现将不得不改变,因为它具有已定义和记录的返回值.(十进制)0.01f返回3位数,因为它是具有三个数字的十进制对象.如果问题是"在double/float中有多少位数",那么是否转换为Decimal并使用GetBits可能无法得到您想要的答案.因为double/float的转换/转换将是不精确的. (2认同)
  • Decimal 类的一个非常重要的用途是所谓的有效数字(我建议您阅读它们)。尽管您可能会觉得 0.01 和 0.010 完全相同,但并非所有人都会这么认为。这是 Decimal 类的一大用途。你说字面值 0.010m 有 2 位还是 3 位数字? (2认同)
  • 虽然这个方法很巧妙,但它并没有达到预期的效果。它认为用值 1234.5600M 初始化的小数有 4dp。请参阅 https://dotnetfiddle.net/FsP49s (2认同)

G.Y*_*G.Y 20

由于所提供的答案都不足以将幻数"-0.01f"转换为十进制...即:GetDecimal((decimal)-0.01f);
我只能假设一个巨大的心灵屁病毒3年前袭击了所有人:)
这里似乎是一个工作实现这个邪恶和怪异的问题,在点之后计算小数位的非常复杂的问题 - 没有字符串,没有文化,不需要计算位数,也不需要阅读数学论坛..只是简单的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;
    }
}
Run Code Online (Sandbox Code Playgroud)
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
}
Run Code Online (Sandbox Code Playgroud)

  • 也许这不是OP想要的,但这个答案更适合我的需要而不是这个问题的最佳答案 (7认同)
  • 对于包含尾随零且数字非常重要的多个案例,您的解决方案将失败.0.01m*2.0m = 0.020m.应该是3位数,你的方法返回2.你似乎错误地理解当你将0.01f转换为Decimal时会发生什么.浮点本质上不精确,因此存储0.01f的实际二进制值并不精确.当你转换为十进制(一个非常结构化的数字符号)时,你可能得不到0.01米(实际上你得到0.010米).GetBits解决方案实际上对于从Decimal获取位数是正确的.如何转换为十进制是关键. (5认同)
  • OP特别说:"19.0应该返回1".在这种情况下,此代码失败. (5认同)
  • @Nicholi 0.020m 等于 0.02m .. 尾随零不重要。OP 在标题中询问“不管文化如何”,更具体的解释是“..跨不同文化信息使用将是安全的..” - 因此我认为我的回答仍然比其他人更有效。 (3认同)
  • 前两行应替换为`n = n % 1; if (n < 0) n = -n;` 因为大于 `int.MaxValue` 的值将导致 `OverflowException`,例如 `2147483648.12345`。 (2认同)

Joe*_*Joe 18

我可能会在@ fixagon的答案中使用这个解决方案.

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

这可能比作为字符串格式化更快,尽管你必须处理大量的小数来注意区别.

我将把实施作为练习.


Kri*_*rov 15

找到小数点后的位数的最佳解决方案之一显示在burning_LEGION的帖子中.

这里我使用的是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 };
}
Run Code Online (Sandbox Code Playgroud)

返回数组的第四个元素包含比例因子和符号.并且正如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;
}
Run Code Online (Sandbox Code Playgroud)

这里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 (value.flags & ~Int32.MinValue) >> 16

        return Expression.Lambda<GetDigitsDelegate>(digits, value);
    }
}
Run Code Online (Sandbox Code Playgroud)

此编译的代码分配给GetDigits字段.请注意,该函数接收十进制值作为ref,因此不执行实际复制 - 仅对值的引用.使用DecimalHelper中的GetDigits函数很简单:

decimal value = 3.14159m;
int digits = DecimalHelper.Instance.GetDigits(ref value);
Run Code Online (Sandbox Code Playgroud)

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

  • 它们的价值相同,但不是有效数字.这是Decimal类的一个大用例.如果我问文字0.010米有多少位数,你会说只有2位吗?尽管全球数十名数学/科学教师会告诉你最后的0是重要的吗?我们所指的问题表现为从浮点数转换为Decimal.不是GetBits本身的使用,它完全按照记录的方式进行.如果你不关心有效数字,那么你有问题,可能不应该首先使用Decimal类. (5认同)
  • 注意:关于整个(十进制)0.01f的事情,你正在为一个非常结构化的像Decimal的东西投射浮点,本质上不是精确的.看一下Console.WriteLine的输出((Decimal)0.01f).在演员阵容中形成的十进制有三位数,这就是为什么提供的所有解决方案都说3而不是2.一切都按预期工作,"问题"是你期望浮点值准确.他们不是. (4认同)
  • 十进制r =(十进制)-0.01f; 和解决方案失败.(关于我在这个页面看到的所有答案......):) (3认同)

fix*_*gon 11

你可以使用InvariantCulture

string priceSameInAllCultures = price.ToString(System.Globalization.CultureInfo.InvariantCulture);
Run Code Online (Sandbox Code Playgroud)

另一种可能性是这样做:

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);
}
Run Code Online (Sandbox Code Playgroud)


Cle*_*ent 10

依赖于小数的内部表示并不酷.

这个怎么样:

    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();
    }
Run Code Online (Sandbox Code Playgroud)


Zas*_*tai 6

这里的大多数人似乎都不知道小数会将尾随零视为存储和打印的重要因素.

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

因此,根据条款,报告"精度太高"的解决方案实际上报告了正确的精度decimal.

此外,基于数学的解决方案(如乘以10的幂)可能会非常慢(十进制比算术的两倍慢约40倍,并且您不希望混合使用浮点,因为这可能会导致不精确).类似地,转换intlong作为截断的手段是容易出错的(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;
}
Run Code Online (Sandbox Code Playgroud)

不变的文化保证了'.' 作为小数点,尾随零被修剪,然后它只是看到小数点后剩余多少个位置(如果有一个).

编辑:将返回类型更改为int


Rit*_*ieD 5

这是另一种方法,使用类型SqlDecimal,它具有scale属性,其中包含小数点右边的数字.将您的十进制值转换为SqlDecimal,然后访问Scale.

((SqlDecimal)(decimal)yourValue).Scale
Run Code Online (Sandbox Code Playgroud)


byt*_*dev 5

作为十进制扩展方法,考虑到:

  • 不同的文化
  • 整数
  • 负数
  • 小数点后置零(例如 1.2300M 将返回 2 而不是 4)
public static class DecimalExtensions
{
    public static int GetNumberDecimalPlaces(this decimal source)
    {
        var parts = source.ToString(CultureInfo.InvariantCulture).Split('.');

        if (parts.Length < 2)
            return 0;

        return parts[1].TrimEnd('0').Length;
    }
}
Run Code Online (Sandbox Code Playgroud)