вторник, 12 июня 2012 г.

Действительные числа в компьютерном мире

Если вы изучали основы работы компьютера или же имели дело с программированием для этой чудо-машины, вы, возможно, помните, что вам запрещали сравнивать действительные числа. По крайней мере говорили делать это с крайней осторожностью.
Лично я совершенно позабыл причины осторожной работы с действительными числами. Недавно, выполняя небольшое задание, столкнулся с требованием: «… мы надеемся не увидеть применения оператора == к операндам типа double…». Решил освежить в памяти вопрос. Начну, пожалуй с маленького примера (1):
Пример 1:
double x = 1.0;
while (x != 0.0)
{
x = x / 2;
       Console.WriteLine(x);
}

В реальном мире цикл должен выполняться бесконечно. На деле же, если вы запустите программку, то зацикливания не произойдёт:



Объясняется это несложно. Мы знаем, что для хранения действительных чисел компьютер отводит конечное число байт. Длинные числа округляются таким образом, чтоб влезали в это количество байт. В цикле мы постоянно делим x на 2. Таким образом x стремится к очень маленькому и очень длинному числу. Выражаясь языком математики, x бесконечно стремится к нулю, но никогда не принимает нулевое значение. Так происходит в жизни реальной. В жизни компьютерной, когда x выходит за пределы объёма отведённой ему памяти, он округляется до нуля.

Пример 2:


static void Main(string[] args)
        {
            for (int i = 10; i > -10; i--)
            {
                try
                {
                    Console.WriteLine(5 / i);
                }
                catch (Exception exep)
                {
                    Console.WriteLine(exep.Message);
                }
            }
            Console.ReadKey();
        }

Вывод для данного фрагмента следующий:


Видно, что при попытке выполнить операцию 5/0 генерируется исключительная ситуация. Этого и следовало ожидать. Теперь же смените в заголовке цикла фрагмент

int i = 10;

на

double i = 10;

Рассмотрим результат выполнения фрагмента:



Теперь же при попытке выполнить операцию 5/0 исключительная ситуация не генерируется, а результатом операции становится бесконечность. Данный факт наводит на мысль о том, что действительные числа в компьютере хранятся неточно.
Ребята со stackoverflow прокомментировали мой вопрос, созвучный с заголовком данной статейки. Для желающих копать глубже, читайте следующее:


Выводы:

Действительные числа не портируемы. В разных системах одно и тоже число будет представлено с разным порогом точности и соответственно округлено по-разному.
Действительные числа в компьютере не всегда подчиняются математическим правилам. Операции сравнения минимально отличающихся друг от друга действительных чисел опасны. Особенно опасны операции проверки на равенство и неравенство.
Вместо того, чтобы писать

    double a, b;
    ...
    if(a == b)
    {
        ...
    }

используйте нечто в стиле

static const double SMALL_NUMBER = 0.000000000001;
inline bool FloatNumbersAreEqual(double a, double b)
{
 return (fabs((a) - (b)) < SMALL_NUMBER);
}

Комментируйте пожалуйста.


Комментариев нет:

Отправить комментарий