在数学上确定十进制值

时间:2015-11-03 02:24:05

标签: c# .net math decimal precision

我一直在寻找某种方法来确定C#中小数的比例和精度,这导致了几个SO问题,但它们似乎没有正确的答案,或者有误导性的标题(它们真的是关于SQL的服务器或其他一些数据库,而不是C#),或任何答案。我认为,以下帖子与我之后的内容最为接近,但即使这样也是错误的:

Determine the decimal precision of an input number

首先,似乎对比例和精度之间的差异存在一些混淆。每Google(根据MSDN):

  

精确度是数字中的位数。 Scale是数字中小数点右边的位数。

话虽如此,数字12345.67890M的标度为5,精度为10.我没有发现一个能在C#中准确计算出来的代码示例。

我想制作两个辅助方法decimal.Scale()decimal.Precision(),以便通过以下单元测试:

[TestMethod]
public void ScaleAndPrecisionTest()
{
    //arrange 
    var number = 12345.67890M;

    //act
    var scale = number.Scale();
    var precision = number.Precision();

    //assert
    Assert.IsTrue(precision == 10);
    Assert.IsTrue(scale == 5);
}

但我还没有找到一个可以执行此操作的代码段,虽然有些人建议使用decimal.GetBits(),而其他人已经说过,将其转换为字符串并解析它。

将它转换为字符串并解析它在我看来是一个糟糕的想法,甚至忽略了小数点的本地化问题。然而,GetBits()方法背后的数学对我来说就像希腊语。

有人可以描述在C#的decimal值中确定比例和精度的计算结果吗?

3 个答案:

答案 0 :(得分:4)

这是使用GetBits() function

获得比例的方法
decimal x = 12345.67890M;
int[] bits = decimal.GetBits(x);
byte scale = (byte) ((bits[3] >> 16) & 0x7F); 

我能想到获得精度的最好方法是删除分数点(即使用Decimal Constructor重构十进制数而不使用上面提到的比例)然后使用对数:

decimal x = 12345.67890M;
int[] bits = decimal.GetBits(x);
//We will use false for the sign (false =  positive), because we don't care about it.
//We will use 0 for the last argument instead of bits[3] to eliminate the fraction point.
decimal xx = new Decimal(bits[0], bits[1], bits[2], false, 0);
int precision = (int)Math.Floor(Math.Log10((double)xx)) + 1;

现在我们可以将它们加入扩展名:

public static class Extensions{
    public static int GetScale(this decimal value){
    if(value == 0)
            return 0;
    int[] bits = decimal.GetBits(value);
    return (int) ((bits[3] >> 16) & 0x7F); 
    }

    public static int GetPrecision(this decimal value){
    if(value == 0)
        return 0;
    int[] bits = decimal.GetBits(value);
    //We will use false for the sign (false =  positive), because we don't care about it.
    //We will use 0 for the last argument instead of bits[3] to eliminate the fraction point.
    decimal d = new Decimal(bits[0], bits[1], bits[2], false, 0);
    return (int)Math.Floor(Math.Log10((double)d)) + 1;
    }
}

这是一个fiddle

答案 1 :(得分:1)

首先,解决“物理”问题:你将如何决定哪些数字是重要的。事实是,"precision" has no physical meaning unless you know or guess the absolute error

现在,有两种基本方法可以确定每个数字(因此,它们的数量):

  • 获取+解释有意义的部分
  • 以数学方式计算

第二种方法无法检测小数部分中的尾随零(根据您对“物理”问题的回答,可能会有或可能不重要),所以除非有要求,否则我不会覆盖它。

对于第一个,在Decimal's interface中,我看到了两种获取部分的基本方法:ToString()(一些重载)和GetBits()

  1. ToString(String, IFormatInfo) 实际上是一种可靠的方式,因为您可以准确定义格式。

  2. GetBits()结果的语义清楚地记录在its MSDN article中(所以感叹“这对我来说是希腊语”不会这样做;))。使用ILSpy进行反编译表明它实际上是对象的原始数据字段的元组:

    public static int[] GetBits(decimal d)
    {
        return new int[]
        {
            d.lo,
            d.mid,
            d.hi,
            d.flags
        };
    }
    

    他们的语义是:

    • |high|mid|low| - 二进制数字(96位),解释为整数(=右对齐)
    • flags
      • 1623 - “10的幂除以整数”(=小数位数)
        • (因此(flags>>16)&0xFF是此字段的原始值)
      • 31 - 签名(与我们无关)

    如您所见,这与IEEE 754 floats非常相似。

    因此,小数位数 指数值总数字数 the number of digits in the decimal representation of the 96-bit integer

答案 2 :(得分:-2)

Racil的回答为您提供了decimal的内部比例值的正确值,尽管如果内部表示发生变化,它将会很有趣。

在当前格式中,decimal的精度部分固定为96位,取决于数字,在28到29位十进制数之间。所有.NET decimal值都共享此精度。由于这是常数,因此没有内部值可用于确定它。

你所看到的是数字位数,我们可以从字符串表示中轻松确定。我们也可以同时获得比例或至少使用相同的方法。

public struct DecimalInfo
{
    public int Scale;
    public int Length;

    public override string ToString()
    {
        return string.Format("Scale={0}, Length={1}", Scale, Length);
    }
}

public static class Extensions
{
    public static DecimalInfo GetInfo(this decimal value)
    {
        string decStr = value.ToString().Replace("-", "");
        int decpos = decStr.IndexOf(".");
        int length = decStr.Length - (decpos < 0 ? 0 : 1);
        int scale = decpos < 0 ? 0 : length - decpos;
        return new DecimalInfo { Scale = scale, Length = length };
    }
}