4

在进行算术运算之前,我想有效地确保十进制值至少有 N 位(在下面的示例中 = 3)。

显然我可以使用 then parse 进行格式化"0.000######....#",但它的效率相对较低,我正在寻找一种避免与字符串相互转换的解决方案。

我尝试了以下解决方案:

decimal d = 1.23M;
d = d + 1.000M - 1;
Console.WriteLine("Result = " + d.ToString()); // 1.230

Decimal.MaxValue - 1在调试和发布版本中使用 Visual Studio 2015 编译时,这似乎适用于所有值 <= 。

但我怀疑编译器可能被允许优化 (1.000 - 1)。C# 规范中是否有任何内容可以保证这将始终有效?

还是有更好的解决方案,例如使用Decimal.GetBits

更新

跟进 Jon Skeet 的回答,我之前曾尝试添加0.000M,但这在 dotnetfiddle 上不起作用。所以我很惊讶地看到它Decimal.Add(d, 0.000M)确实有效。 这是一个 dotnetfiddle比较d + 000Mdecimal.Add(d,0.000M):结果与 dotnetfiddle 不同,但在使用 Visual Studio 2015 编译相同代码时相同:

decimal d = 1.23M;
decimal r1 = decimal.Add(d, 0.000M);
decimal r2 = d + 0.000M;
Console.WriteLine("Result1 = " + r1.ToString());  // 1.230 
Console.WriteLine("Result2 = " + r2.ToString());  // 1.23 on dotnetfiddle

因此,至少某些行为似乎是依赖于编译器的,这并不令人放心。

4

2 回答 2

6

如果您担心编译器会优化运算符(尽管我怀疑它是否会这样做),您可以直接调用该Add方法。请注意,您不需要先加然后减 - 您只需添加 0.000m。例如:

public static decimal EnsureThreeDecimalPlaces(decimal input) =>
    decimal.Add(input, 0.000m);

这似乎工作正常 - 如果您对编译器将如何处理常量感到紧张,您可以将这些位保留在一个数组中,只转换一次:

private static readonly decimal ZeroWithThreeDecimals =
    new decimal(new[] { 0, 0, 0, 196608 }); // 0.000m

public static decimal EnsureThreeDecimalPlaces(decimal input) =>
    decimal.Add(input, ZeroWithThreeDecimals);

我认为这有点过头了——特别是如果你有很好的单元测试。(如果您针对将要部署的编译代码进行测试,那么编译器之后就无法进入那里 -看到 JIT 在这里介入,我真的很惊讶。)

于 2017-11-05T14:07:11.840 回答
0

Decimal.ToString() 方法输出由结构的内部比例因子确定的小数位数。此因子的范围可以从 0 到 28。您可以通过调用Decimal.GetBits 方法获取信息以确定此缩放因子。这个方法的名字有点误导,因为它返回一个包含四个整数值的数组,可以传递给Decimal Constructor (Int32[]);我提到这个构造函数的原因是文档的“备注”部分比 GetBits 方法的文档更好地描述了位布局。

使用此信息,您可以确定 Decimal 值的比例因子,从而知道默认ToString方法将产生多少小数位。以下代码将其演示为名为“Scale”的扩展方法。我还包括了一个名为“ToStringMinScale”的扩展方法,用于将 Decimal 格式化为最小比例因子值。如果 Decimal 的比例因子大于指定的最小值,则将使用该值。

internal static class DecimalExtensions
    {
    public static Int32 Scale(this decimal d)
        {
        Int32[] bits = decimal.GetBits(d);

        // From: Decimal Constructor (Int32[]) - Remarks
        // https://msdn.microsoft.com/en-us/library/t1de0ya1(v=vs.100).aspx

        // The binary representation of a Decimal number consists of a 1-bit sign, 
        // a 96-bit integer number, and a scaling factor used to divide 
        // the integer number and specify what portion of it is a decimal fraction. 
        // The scaling factor is implicitly the number 10, raised to an exponent ranging from 0 to 28.

        // bits is a four-element long array of 32-bit signed integers.

        // bits [0], bits [1], and bits [2] contain the low, middle, and high 32 bits of the 96-bit integer number.

        // bits [3] contains the scale factor and sign, and consists of following parts:

        // Bits 0 to 15, the lower word, are unused and must be zero.

        // Bits 16 to 23 must contain an exponent between 0 and 28, which indicates the power of 10 to divide the integer number.

        // Bits 24 to 30 are unused and must be zero.

        // Bit 31 contains the sign; 0 meaning positive, and 1 meaning negative.

        // mask off bits 0 to 15
        Int32 masked = bits[3] & 0xF0000;
        // shift masked value 16 bits to the left to obtain the scaleFactor
        Int32 scaleFactor = masked >> 16;

        return scaleFactor;
        }

    public static string ToStringMinScale(this decimal d, Int32 minScale)
        {
        if (minScale < 0 || minScale > 28)
            {
            throw new ArgumentException("minScale must range from 0 to 28 (inclusive)");
            }
        Int32 scale = Math.Max(d.Scale(), minScale);
        return d.ToString("N" + scale.ToString());
        }

    }
于 2017-11-05T17:19:14.810 回答