.net decimal - 去除比例,保证工作的解决方案

.net decimal - remove scale, solution that is guaranteed to work

我想将小数位 > 0 的小数 a 转换为小数位 0 的等价小数 b(假设存在不失精度的等价小数)。成功的定义是 b.ToString() return 一个没有任何尾随零的字符串,或者通过 GetBits 提取比例并确认它是 0。

我找到的简单选项:

        Decimal scale2 = new Decimal(100, 0, 0, false, 2);
        string scale2AsString = scale2.ToString(System.Globalization.CultureInfo.InvariantCulture);
        // toString includes trailing zeroes
        Assert.IsTrue(scale2AsString.Equals("1.00"));

        // can use format specifier to specify format
        string scale2Formatted = scale2.ToString("G0");
        Assert.IsTrue(scale2Formatted.Equals("1"));

        // but what if we want to pass the decimal to third party code that does not use format specifiers?

        // option 1, use Decimal.Truncate or Math.Truncate (Math.Truncate calls Decimal.Truncate, I believe)
        Decimal truncated = Decimal.Truncate(scale2);
        string truncatedAsString = truncated.ToString(System.Globalization.CultureInfo.InvariantCulture);
        Assert.IsTrue(truncatedAsString.Equals("1"));

        // option 2, division trick
        Decimal divided = scale2 / 1.000000000000000000000000000000000m;
        string dividedAsString = divided.ToString(System.Globalization.CultureInfo.InvariantCulture);
        Assert.IsTrue(dividedAsString.Equals("1"));

        // option 3, if we expect the decimal to fit in int64, convert to int64 and back
        Int64 asInt64 = Decimal.ToInt64(scale2);
        Decimal backToDecimal = new Decimal(asInt64);
        string backToDecimalString = backToDecimal.ToString(System.Globalization.CultureInfo.InvariantCulture);
        Assert.IsTrue(backToDecimalString.Equals("1"));

        // option 4, convert to BigInteger then back using BigInteger's explicit conversion to decimal
        BigInteger convertedToBigInteger = new BigInteger(scale2);
        Decimal bigIntegerBackToDecimal = (Decimal)convertedToBigInteger;
        string bigIntegerBackToDecimalString = bigIntegerBackToDecimal.ToString(System.Globalization.CultureInfo.InvariantCulture);
        Assert.IsTrue(bigIntegerBackToDecimalString.Equals("1"));

这么多选择,当然还有更多。但是这些选项中的哪些实际上可以保证有效?

选项 1:MSDN does not mention that the scale is changed when calling Truncate, so using this method seems to be relying on an implementation detail. Internally, Truncate calls FCallTruncate,我没有找到任何文档。

选项 2 may be mandated by the CLI spec,但我没有找到确切的规范,我没有在 ECMA 规范中找到它。

选项 3(ToInt64 也使用 FCallTruncate internally) will work judging by the reference source (the constructor taking an ulong sets flags and this scale to 0) but the documentation 再次未提及比例。

选项 4,BigInteger calls Decimal.Truncate,注释:

        // First truncate to get scale to 0 and extract bits
        int[] bits = Decimal.GetBits(Decimal.Truncate(value));

很明显,Microsoft 内部也认为 Decimal.Truncate 会将比例设置为 0。

但我正在寻找一种保证在不依赖实现细节的情况下工作并且适用于技术上可以工作的所有小数的方法(例如无法重新缩放 Decimal.MaxValue)。 None 以上选项似乎符合此要求。

您几乎自己回答了您的问题。就个人而言,我不会太在意使用哪种方法。如果您的方法现在有效——即使它没有记录——那么它很可能在将来有效。如果将来对 .NET 的更新破坏了您的方法,那么希望您有一个测试可以在您升级应用程序框架时突出显示这一点。

查看您的选项:

1) Decimal.Truncate 将比例设置为 0,但如果它没有记录,那么您可能决定不依赖这个事实。

2) 除以 1.0000 ... 可能会给你想要的结果,但不清楚发生了什么,如果没有记录,那么这可能是最糟糕的选择。

3 和 4) 这些选项应该适合您。您将小数转换为整数,然后再转换回小数。显然,从整数创建的小数位数为 0。即使没有明确记录,任何其他值都是错误的。选项 4) 甚至可以处理 Decimal.MaxValueDecimal.MinValue.