【问题标题】:Mathematically determine the precision and scale of a decimal value以数学方式确定十进制值的精度和小数位数
【发布时间】:2019-01-15 00:35:45
【问题描述】:

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

Determine the decimal precision of an input number

首先,对于规模和精度之间的差异似乎有些混淆。每谷歌(每 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);
}

但我还没有找到一个可以做到这一点的 sn-p,虽然有几个人建议使用 decimal.GetBits(),其他人说,将其转换为字符串并解析它。

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

谁能描述在 C# 的 decimal 值中确定比例和精度的计算是什么样的?

【问题讨论】:

  • 你能解释一下为什么你认为精度是 10 吗?该数字有 11 位数字,后面有两个零,剩下 9 个明显有效的数字。我没有看到 10 是从哪里来的。
  • 你的编辑不会改变比例吗?
  • ...是的。是的。我很烂。修复它
  • 你们中的哪一位能向我解释一下编写一个像手动计数一样计数数字的函数有多难吗?无论如何,这些功能有什么好处?
  • @JeremyHolovacs 虽然我同意你关于将它转换为字符串的味道,但没有“数学”方法可以给你这些答案,仅仅是因为它们不是数学概念。这些概念是存储概念。例如,您对 Ron 说“在 C# 中,尾随零并非无关紧要”。好吧,我们可以同意它们在数学上微不足道吗?如果我们同意这一点,那么数学根本无法给你想要的答案。无论如何,我仍然想知道这些功能的好处。

标签: c# .net math decimal precision


【解决方案1】:

这是您使用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

【讨论】:

  • @ivan_pozdeev 我试图找出获得精度的最佳方法。我添加了指向 MSDN 的链接。抱歉,我认为重申 MSDN 完美解释的内容没有真正意义,但您在这方面做得很好。但是你指的是什么错误?
  • 优秀。这似乎适用于我可以抛出的所有边缘情况。
  • @RacilHilan MSDN 的解释对我来说不够好,所以我做了我认为更好的工作。至于错误,它是&0x7F 而不是&0xFF(第 16 位到第 23 位是 8 位)。这不会在在正常情况下表现出来,因为最高的合法值是 28,但它仍然违反规范,类似于放置地雷让某人最终踩到(例如,它会导致如果您以某种方式获得(例如反序列化)格式错误的十进制,则您的代码与 .NET 的代码之间存在差异。如果您认为有任何方法可以从您的答案中找出答案,请向我扔石头。
  • @ivan_pozdeev 我不从事石材业务。 :-) 我确实说过你做得很好,不是吗?至于&0x7F,您关于规格的观点是有效的,但它是从 MSDN 复制/粘贴的。如果您认为这是错误的,您可以在该页面上发表评论,以便他们修复它。但是,我在函数中使用了它,该函数将比例返回为整数,这对于序列化或任何其他用途是完全安全的。
  • 0.000m == 0 为真,因此需要移除零短路。
【解决方案2】:

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


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

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

第二种方法无法检测小数部分的尾随零(这可能会或可能不会很重要,具体取决于您对“物理”问题的回答),因此除非要求,否则我不会介绍它。

对于第一个,在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:
      • bits 16 to 23 - “10 的幂除以整数”(=小数位数)
        • (因此(flags>>16)&0xFF 是该字段的原始值)
      • bit 31 - 签名(与我们无关)

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

    所以,小数位数就是指数值总位数the number of digits in the decimal representation of the 96-bit integer

【讨论】:

  • 这并没有回答问题,也几乎忽略了这个问题。
  • 我的目标不仅仅是给出一个“试试这个”代码“答案”,而是演示如何生成一个,以便您了解自己在做什么以及为什么要这样做。每种方法的要点以粗体突出显示。它们不直接引用“比例”和“精度”,因为正如前导部分所说,这些术语可能意味着不同的东西,具体取决于您的应用程序。
【解决方案3】:

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

【讨论】:

  • 这几乎是我明确表示不想做的事情。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-15
  • 2011-03-02
  • 2013-07-17
  • 1970-01-01
  • 1970-01-01
  • 2014-08-14
相关资源
最近更新 更多