Некорректное вычитание double. В чем проблема, как исправить? [дубликат]

73
07 марта 2022, 11:00
На этот вопрос уже даны ответы здесь:
Вычисления на числах с плавающей точкой не работают (2 ответа)
Закрыт 2 года назад.
            double dTest1 = Convert.ToDouble("1005,502247");
            double dTest2 = Convert.ToDouble("1005,142247");
            double dTest3 ;
            dTest3 = (dTest1 - dTest2);

На выходе в отладчике dTest3 0.36000000000001364. Откуда хвост, что это, как исправлять?

Answer 1

Это проблема проявляется не только с C#, но и в C++, js, python, java etc - везде, где есть тип числа с плавающей точкой, и связана с тем, как число с плавающей точкой вычисляется и хранится в памяти компьютера. И касается она не только double, но и float.

Если вдаться в подробности, то число с плавающей точкой типа double(float) хранится в памяти в виде мантисы и экспоненты в двоичном виде. При чем длина мантисы ограничена 23 битами для float, и 52 - для double, а экспоненты - 8-ю и 11-ю соответственно. В этом случае значение переменной вычисляется по формуле: (-1)^sign* (1.m) * (2 ^ e). Такой подход обеспечивает точность не более 7-ми знаков после запятой для float и 15 - для double.

Как происходит запись?

Возьмём пример числа:

double d = 12;

12 = (8+4)=(1+1/2)*2^3. - здесь экспонента будет равна 100 (3 в бинарном виде), а мантисса - 1 (поскольку 1/2 - в бинарном виде 0.1).. - это простой пример, и он вполне может храниться в памяти без потери. От простого к сложному:

2.1 = (2 + 0.1)=(1+0.05)*2

Тут уже e=1 А вот m=?

Здесь ждёт сюрприз:

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

m = 00001100110011001100110011001100110011001100110011

Очевидно, что при хранении этого числа будет потеряна точность.

Что можно почитать на тему:

  • Обсуждение операций над double на хабр
  • Там же о том, как хранится в памяти переменная с плавающей точкой
  • Офф. Доки Microsoft
  • Просто неплохая статья

Решение:

Для решения сложившейся ситуации есть несколько путей:

  1. Решение в лоб: округлить результат выполнения операций над double/float с помощью Math.Round:

    var r = Math.Round(d, 2);

  2. Использовать специальный тип для хранения чисел с плавающей точкой под названием Decimal. Это надёжное и впрочем рекомендуемое решение, но нужно учесть, что при больших вычислениях может быть незначитедьная потеря в производительности

READ ALSO
Не закрывать базовый поток

Не закрывать базовый поток

Можно ли как-то сделать так, что бы не вызывался Dispose при использовании оберток StreamReader / StreamWriter?

86
Выводит удаленные записи из БД

Выводит удаленные записи из БД

вот моя настройка nhibernate где я говорю, что не выводить записи у которых "IsDeleted = true" в БД :

71
Количество символов в HTML, в поле типа TEXT, БД PostgreSQL

Количество символов в HTML, в поле типа TEXT, БД PostgreSQL

Подскажите, как можно подсчитать количество символов в поле типа TEXT в БД PostgreSQL, подсчитать количество символов без пробелов, HTML-ссылок, спецсимволов?

83
Как изменить кодировку сайта

Как изменить кодировку сайта

Как перевести сайт с UTF8 на windows-1251?

154