How to efficiently provide a decimal value has at least N decimal places

I want to effectively ensure that the decimal value has at least N (= 3 in the example below) before performing arithmetic operations.

Obviouly I could format with "0.000######....#", then parse, but it is relatively inefficient, and I'm looking for a solution that avoids converting to / from string.

I tried the following solution:

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

which seems to work for all <= values Decimal.MaxValue - 1when compiling using Visual Studio 2015 in both Debug and Release builds.

But I have a suspicion that compilers are allowed to optimize (1.000 - 1). Is there anything in the C # specification that guarantees that this will always work?

Or is there a better solution, for example. using Decimal.GetBits?

UPDATE

Following John Skeet's answer, I was already trying to add 0.000M, but this did not work on dotnetfiddle. Therefore, I was surprised to see what Decimal.Add(d, 0.000M)works. Here dotnetfiddle compares d + 000Mand decimal.Add(d,0.000M): the results are different from dotnetfiddle, but they are identical when the same code is compiled using 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

Thus, at least some behavior seems to depend on the compiler, which is not reassuring.

+4
source share
2 answers

, ( , - ), 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.)

+6

Decimal.ToString() , . 0 28. , Decimal.GetBits Method. , , Decimal Constructor (Int32 []); , , , "" , GetBits.

, , , , ToString . "". "ToStringMinScale", . , .

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

    }
0

Source: https://habr.com/ru/post/1688756/


All Articles