Подтвердить что ты не робот

Интересное поведение типа "десятичный" в С#

Если объявить заполнение как const decimal, заполнение не будет работать.

mymoney = 1.2 и ваши деньги = 1.20, как объяснить это поведение?

class Program
{
    static void Main(string[] args)
    {
        decimal balance = 1.2m;

        const decimal ConstPadding = 0.00m;

        decimal padding = 0.00m;
        decimal mymoney = decimal.Round(balance + ConstPadding, 2);
        decimal yourmoney =  decimal.Round(balance + padding, 2);

        Console.WriteLine(mymoney); // 1.2
        Console.WriteLine(yourmoney);  //1.20
    }
}
4b9b3361

Ответ 1

В качестве аккомпанемента для Jon ответьте ниже, это IL, созданный из вашего кода. По его словам, mymoney никогда не добавлялся.

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       61 (0x3d)
  .maxstack  6
  .locals init ([0] valuetype [mscorlib]System.Decimal balance,
           [1] valuetype [mscorlib]System.Decimal padding,
           [2] valuetype [mscorlib]System.Decimal mymoney,
           [3] valuetype [mscorlib]System.Decimal yourmoney)
  IL_0000:  nop
  IL_0001:  ldc.i4.s   12
  IL_0003:  ldc.i4.0
  IL_0004:  ldc.i4.0
  IL_0005:  ldc.i4.0
  IL_0006:  ldc.i4.1
  IL_0007:  newobj     instance void [mscorlib]System.Decimal::.ctor(int32,
                                                                     int32,
                                                                     int32,
                                                                     bool,
                                                                     uint8)
  IL_000c:  stloc.0
  IL_000d:  ldc.i4.0
  IL_000e:  ldc.i4.0
  IL_000f:  ldc.i4.0
  IL_0010:  ldc.i4.0
  IL_0011:  ldc.i4.2
  IL_0012:  newobj     instance void [mscorlib]System.Decimal::.ctor(int32,
                                                                     int32,
                                                                     int32,
                                                                     bool,
                                                                     uint8)
  IL_0017:  stloc.1
  IL_0018:  ldloc.0
  IL_0019:  ldc.i4.2
  IL_001a:  call       valuetype [mscorlib]System.Decimal [mscorlib]System.Decimal::Round(valuetype [mscorlib]System.Decimal,
                                                                                          int32)
  IL_001f:  stloc.2
  IL_0020:  ldloc.0
  IL_0021:  ldloc.1
  IL_0022:  call       valuetype [mscorlib]System.Decimal [mscorlib]System.Decimal::op_Addition(valuetype [mscorlib]System.Decimal,
                                                                                                valuetype [mscorlib]System.Decimal)
  IL_0027:  ldc.i4.2
  IL_0028:  call       valuetype [mscorlib]System.Decimal [mscorlib]System.Decimal::Round(valuetype [mscorlib]System.Decimal,
                                                                                          int32)
  IL_002d:  stloc.3
  IL_002e:  ldloc.2
  IL_002f:  call       void [mscorlib]System.Console::WriteLine(valuetype [mscorlib]System.Decimal)
  IL_0034:  nop
  IL_0035:  ldloc.3
  IL_0036:  call       void [mscorlib]System.Console::WriteLine(valuetype [mscorlib]System.Decimal)
  IL_003b:  nop
  IL_003c:  ret
} // end of method Program::Main

Чтобы создать IL (т.е. если вы хотите посмотреть под капотом в будущем), просто запустите ILDASM из командной строки VS, затем загрузите исполняемый файл и дважды щелкните по методу, который вы хотите посмотреть.

Ответ 2

Компилятор "знает", что добавление нуля к значению "не должно" изменять значение - поэтому он оптимизирует это. Теперь возможно, что недопустимая оптимизация с учетом характера десятичного сложения, но если вы посмотрите на сгенерированный код, вы обнаружите, что вычисление mymoney не связано с добавлением.

Я не думаю, что попытаюсь использовать добавление 0.00m как способ обеспечить конкретный масштаб, если честно. Вы можете создать свой собственный код для обеспечения масштабирования, используя decimal.GetBits и конструктор, выполняющий обратную операцию - но я не думаю, что было бы ужасно хорошо.

Вам определенно нужна эта форма "двух десятичных знаков" как промежуточное значение, или она предназначена только для презентации? Если это последнее, я бы посмотрел на строки формата.

Ответ 3

balance + ConstPadding == balance

Поскольку ConstPadding равен нулю!

Вы должны -

Console.WriteLine(yourmoney.ToString("0.00"));  //1.20

Ответ 4

Операция суммы с постоянным заполнением полностью исключается из MSIL, но она существует для непостоянного поля. К сожалению, я не смог найти ссылки на функцию FCallAddSub, но тот, кто "оптимизирует" вызов.

Ответ 5

Если я не ошибаюсь, то complier не выполняет добавление с константой decimal, потому что она равна нулю.

Скоро опубликует доказательство.

Доказательство, показанное Джоном Скитом, выше.