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

С# десятичное умножение странное поведение

Я заметил странное поведение при умножении десятичных значений на С#. Рассмотрим следующие операции умножения:

1.1111111111111111111111111111m * 1m = 1.1111111111111111111111111111 // OK
1.1111111111111111111111111111m * 2m = 2.2222222222222222222222222222 // OK
1.1111111111111111111111111111m * 3m = 3.3333333333333333333333333333 // OK
1.1111111111111111111111111111m * 4m = 4.4444444444444444444444444444 // OK
1.1111111111111111111111111111m * 5m = 5.5555555555555555555555555555 // OK
1.1111111111111111111111111111m * 6m = 6.6666666666666666666666666666 // OK
1.1111111111111111111111111111m * 7m = 7.7777777777777777777777777777 // OK
1.1111111111111111111111111111m * 8m = 8.888888888888888888888888889  // Why not 8.8888888888888888888888888888 ?
1.1111111111111111111111111111m * 9m = 10.000000000000000000000000000 // Why not 9.9999999999999999999999999999 ?

То, что я не могу понять, это два последних случая. Как это возможно?

4b9b3361

Ответ 1

decimal хранит 28 или 29 значащих цифр (96 бит). В основном мантисса находится в диапазоне -/+ 79,228,162,514,264,337,593,543,950,335.

Это означает, что примерно до 7.9.... вы можете получить 29 значащих цифр точно, но выше этого вы не сможете. Вот почему и 8, и 9 ошибаются, но не более ранние значения. Вы должны полагаться только на 28 значащих цифр в целом, чтобы избежать подобных ситуаций.

Как только вы уменьшите исходный ввод до 28 значащих цифр, вы получите ожидаемый результат:

using System;

class Test
{
    static void Main()
    {
        var input = 1.111111111111111111111111111m;
        for (int i = 1; i < 10; i++)
        {
            decimal output = input * (decimal) i;
            Console.WriteLine(output);
        }
    }
}

Ответ 2

Математики различают рациональные числа и вещественные числа надмножеств. Арифметические операции над рациональными числами хорошо определены и точны. Арифметика (с использованием операторов сложения, вычитания, умножения и деления) на вещественные числа является "точной" только в той степени, в которой либо иррациональные числа остаются в иррациональной форме (символической), либо, возможно, конвертируемы в некоторых выражениях к рациональному числу, Например, квадратный корень из двух не имеет десятичного (или любого другого рационального базового) представления. Однако квадратный корень из двух, умноженных на квадратный корень из двух, является рациональным - 2, очевидно.

Компьютеры и языки, запущенные на них, обычно реализуют только рациональные числа - скрытые за такими именами, как int, long int, float, double precision, real (FORTRAN) или другое имя, которое предлагает реальные числа. Но включенные рациональные числа ограничены, в отличие от множества рациональных чисел, диапазон которых бесконечен.

Тривиальный пример - не найден на компьютерах. 1/2 * 1/2 = 1/4 Это отлично работает, если у вас есть класс рациональных чисел И размер числителей и знаменателей не превышает пределов целочисленной арифметики. поэтому (1,2) * (1,2) → (1,4)

Но если доступные рациональные числа были десятичными и были ограничены одной цифрой после десятичной - непрактичной - но представителем выбора, сделанного при выборе реализации для аппроксимации рациональных чисел (float/real и т.д.), то 1/2 было бы идеально конвертируемым до 0,5, тогда 0,5 + 0,5 было бы равно 1,0, но 0,5 * 0,5 должно было бы быть либо 0,2, либо 0,3!