查找十进制值小数点后的位数,无论文化(Find number of decimal places i

2019-06-21 06:09发布

我不知道是否有退出小数位的数量十进制值(作为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;

但它发生,我认为这只会在使用该地区的工作“” 因此作为小数分隔并且是在不同的系统非常脆。

Answer 1:

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

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


Answer 2:

由于没有提供答案者为神奇的数字足够好“-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
}


Answer 3:

我可能会使用该解决方案@ fixagon的答案 。

然而,虽然小数结构没有一个方法来获得小数号码,你可以调用Decimal.GetBits提取二进制表示,然后使用整数值和比例来计算的小数位数。

这可能比格式化为一个字符串更快,但你必须要处理非常多的小数来发现其中的差别。

我会离开的实现作为一个练习。



Answer 4:

一为小数点后发现的位数的最佳解决方案如图burning_LEGION的职位 。

在这里,我使用的部件从STSdb论坛文章: 中位数小数点后 。

在MSDN,我们可以阅读以下说明:

“A十进制数是指示浮动小数点的,所述整数和小数分离的位置的浮点值,它由一个符号,数字值,其中在所述值的每个数字范围从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#中的私有方法 ,我们可以使用表达式为建设标志领域的直接访问,避免建设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);

这是小数点的十进制值后得到的位数尽可能快的方法。



Answer 5:

您可以使用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);
}


Answer 6:

依托小数的内部表示不凉。

这个怎么样:

    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();
    }


Answer 7:

而这里的另一种方法,使用具有与十进制的数字权数的规模属性的类型SQLDECIMAL。 您的十进制值投射到SQLDECIMAL,然后访问规模。

((SqlDecimal)(decimal)yourValue).Scale


Answer 8:

这里大多数人似乎没有察觉到小数点认为尾随零作为存储和打印显著。

所以0.1米,0.10米和0.100米可比较结果为相等,将它们存储不同(如值/尺度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;
}

固定区域性保证了“” 如小数点,尾随零的修整,然后它只是一个多少位置保持小数点后,看不到的事情(如果有的话甚至是一个)。

编辑:改变返回类型为int



Answer 9:

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

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;


Answer 10:

到目前为止,几乎所有的上市方案被分配GC内存,这是非常多的C#的方式来做事,但很不理想的性能是至关重要的环境。 (不分配使用的循环,也是那些不采取尾随零考虑。)

因此,为了避免GC Allocs,你可以访问不安全的上下文比例位数。 这听起来脆弱,但按照微软的参考源 ,小数的结构布局是连续的,甚至有在那里,不改变字段的顺序评论:

    // NOTE: Do not change the order in which these fields are declared. The
    // native methods in this class rely on this particular order.
    private int flags;
    private int hi;
    private int lo;
    private int mid;

正如你所看到的,这里的第一个int是标志字段。 从文档和其他评论讲到这里,我们知道,只有从16-24位编码的规模,我们需要避免编码符号第31位。 因为int是4个字节大小,我们可以放心地做到这一点:

internal static class DecimalExtensions
{
  public static byte GetScale(this decimal value)
  {
    unsafe
    {
      byte* v = (byte*)&value;
      return v[2];
    }
  }
}

这应该是最高效的解决方案,因为在字节阵列或的ToString转换没有GC ALLOC。 我测试过它针对的.Net 4.x版和.Net 3.5在Unity 2019.1。 如果存在该不会失败的任何版本,请让我知道。

编辑:

由于@Zastai提醒我要使用一个明确的结构布局,切实做到不安全代码之外相同的指针逻辑的可能性:

[StructLayout(LayoutKind.Explicit)]
public struct DecimalHelper
{
    const byte k_SignBit = 1 << 7;

    [FieldOffset(0)]
    public decimal Value;

    [FieldOffset(0)]
    public readonly uint Flags;
    [FieldOffset(0)]
    public readonly ushort Reserved;
    [FieldOffset(2)]
    byte m_Scale;
    public byte Scale
    {
        get
        {
            return m_Scale;
        }
        set
        {
            if(value > 28)
                throw new System.ArgumentOutOfRangeException("value", "Scale can't be bigger than 28!")
            m_Scale = value;
        }
    }
    [FieldOffset(3)]
    byte m_SignByte;
    public int Sign
    {
        get
        {
            return m_SignByte > 0 ? -1 : 1;
        }
    }
    public bool Positive
    {
        get
        {
            return (m_SignByte & k_SignBit) > 0 ;
        }
        set
        {
            m_SignByte = value ? (byte)0 : k_SignBit;
        }
    }
    [FieldOffset(4)]
    public uint Hi;
    [FieldOffset(8)]
    public uint Lo;
    [FieldOffset(12)]
    public uint Mid;

    public DecimalHelper(decimal value) : this()
    {
        Value = value;
    }

    public static implicit operator DecimalHelper(decimal value)
    {
        return new DecimalHelper(value);
    }

    public static implicit operator decimal(DecimalHelper value)
    {
        return value.Value;
    }
}

为了解决原来的问题,你可以剥去除了各个领域ValueScale ,但也许这可能是有用的人,让他们所有。



Answer 11:

你可以试试:

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


Answer 12:

我用下面的机制在我的代码

  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;
    }

十进制输入= 3.376; VAR instring = input.ToString();

调用GetDecimalLength(instring)



Answer 13:

string number = "123.456789"; // Convert to string
int length = number.Substring(number.IndexOf(".") + 1).Length;  // 6


Answer 14:

我建议使用此方法:

    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); 
    }


Answer 15:

使用递归,你可以这样做:

private int GetDecimals(decimal n, int decimals = 0)  
{  
    return n % 1 != 0 ? GetDecimals(n * 10, decimals + 1) : decimals;  
}


文章来源: Find number of decimal places in decimal value regardless of culture