以数学方式确定十进制值的精度和小数位数

本文关键字:精度 小数 十进制 方式确 | 更新日期: 2023-09-27 18:20:14

我一直在寻找某种方法来确定 C# 中小数的小数位数和精度,这让我想到了几个 SO 问题,但它们似乎都没有正确答案,或者有误导性的标题(它们实际上是关于 SQL Server 或其他一些数据库,而不是 C#(,或者根本没有任何答案。我认为,以下帖子最接近我所追求的,但即使这样似乎也是错误的:

确定输入数字的小数精度

首先,对于比例和精度之间的区别似乎存在一些混淆。每个谷歌(每个MSDN(:

精度是数字中的位数。小数位数是数字中小数点右侧的位数。

话虽如此,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值中的小数位数和精度的计算会是什么样子的?

以数学方式确定十进制值的精度和小数位数

以下是使用 GetBits() 函数获取比例的方式:

decimal x = 12345.67890M;
int[] bits = decimal.GetBits(x);
byte scale = (byte) ((bits[3] >> 16) & 0x7F); 

我能想到的获得精度的最好方法是删除分数点(即使用十进制构造函数重建没有上述小数位数的十进制数(,然后使用对数:

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

这是一个小提琴。

首先,解决"物理"问题:你将如何决定哪些数字是重要的。事实是,"精度"没有物理意义,除非你知道或猜测绝对误差。


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

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

第二种方法无法检测小数部分中的尾随零(根据您对"物理"问题的回答,这可能很重要,也可能不重要(,因此除非要求,否则我不会涵盖它。

对于第一个,在Decimal的界面中,我看到了 2 种获取零件的基本方法:ToString()(一些重载(和 GetBits()

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

    • 例如,使用 F 说明符并传递一个非特定区域性NumberFormatInfo,在该中,您手动设置了影响此特定格式的所有字段。
      • 关于NumberDecimalDigits字段:测试显示它是最小数字 - 因此将其设置为 0(文档对此不清楚(,- 如果有的话,尾随零打印得很好
  2. 结果的语义在其 MSDN 文章中清楚地记录GetBits()(因此像"对我来说是希腊语"这样的哀叹不会;)(。使用 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浮点数非常相似。

    因此,小数位数是指数值位数是 96 位整数的十进制表示形式的位数

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