Почему я вижу двойную переменную, инициализированную некоторым значением, например 21.4, как 21.399999618530273?

double r = 11.631;
double theta = 21.4;

В отладчике они отображаются как 11.631000000000000 и 21.399999618530273.

Как я могу избежать этого?

Ответы

Ответ 1

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

Кстати, печать этих значений во время выполнения часто по-прежнему приводит к правильным результатам, по крайней мере, с использованием современных компиляторов С++. Для большинства операций это не большая проблема.

Ответ 2

Мне понравилось Объяснение Joel, в котором рассматривается аналогичная проблема с двоичной точностью с плавающей точкой в ​​Excel 2007:

Посмотрите, как там много конца 0110 0110 0110? Это потому, что 0,1 не имеет точного представления в двоичном формате... это повторяющееся двоичное число. Это похоже на то, как 1/3 не имеет представления в десятичной форме. 1/3 - 0,333333333, и вы должны продолжать писать 3 навсегда. Если вы потеряете терпение, вы получите что-то неточное.

Итак, вы можете себе представить, как в десятичном случае, если вы попытались сделать 3 * 1/3, и у вас не было времени писать 3 навсегда, результат, который вы получите, будет 0.99999999, а не 1, и люди злитесь на вас за то, что вы ошибаетесь.

Ответ 3

Если у вас есть значение, например:

double theta = 21.4;

И вы хотите сделать:

if (theta == 21.4)
{
}

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

if (fabs(theta - 21.4) <= 1e-6)
{
}

Ответ 4

Это частично зависит от платформы - и мы не знаем, какую платформу вы используете.

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

Учитывая, что отладчик видит "фактическое" значение, он должен принять решение о том, что показывать - он может показать вам округленное значение до нескольких десятичных знаков или более точное значение. Некоторые отладчики выполняют лучшую работу, чем другие, при чтении умов разработчиков, но это фундаментальная проблема с двоичными числами с плавающей запятой.

Ответ 5

Используйте тип неподвижной точки decimal, если вы хотите, чтобы стабильность была в пределах точности. Есть накладные расходы, и вы должны явно указать, хотите ли вы конвертировать в плавающую точку. Если вы конвертируете в плавающую точку, вы снова введете неустойчивости, которые, похоже, беспокоят вас.

В качестве альтернативы вы можете преодолеть это и научиться работать с ограниченной точностью арифметики с плавающей запятой. Например, вы можете использовать округление для сближения значений, или вы можете использовать сравнения epsilon для описания допусков. "Эпсилон" - это константа, которую вы настраиваете, которая определяет допуск. Например, вы можете считать, что два значения равны, если они находятся в пределах 0,0001 друг от друга.

Мне кажется, что вы можете использовать перегрузку оператора, чтобы сделать сравнения epsilon прозрачными. Это было бы очень круто.


Для представлений мантиссы-экспоненты EPSILON необходимо вычислить, чтобы оставаться в пределах отображаемой точности. Для числа N Epsilon = N/10E + 14

System.Double.Epsilon - наименьшее представимое положительное значение для типа Double. Это слишком мало для нашей цели. Прочитайте Совет Microsoft по тестированию на равенство

Ответ 6

Я столкнулся с этим раньше (в моем блоге) - Я думаю, что сюрприз, как правило, заключается в том, что "иррациональные" номера отличается.

Под "иррациональным" здесь я просто ссылаюсь на то, что они не могут быть точно представлены в этом формате. Реальные иррациональные числа (например, π - pi) не могут быть точно представлены вообще.

Большинство людей знакомы с тем, что 1/3 не работает в десятичной системе: 0.3333333333333...

Странно, что 1.1 не работает в поплавках. Люди ожидают, что десятичные значения будут работать в числах с плавающей запятой из-за того, как они думают о них:

1,1 составляет 11 x 10 ^ -1

Когда они на самом деле находятся в базе-2

1.1 равен 154811237190861 x 2 ^ -47

Вы не можете этого избежать, вам просто нужно привыкнуть к тому, что некоторые float являются "иррациональными", так же, как и 1/3.

Ответ 7

Один из способов избежать этого - использовать библиотеку, которая использует альтернативный метод представления десятичных чисел, например BCD

Ответ 8

Мне кажется, что 21.399999618530273 является представлением одинарной точности (float) 21.4. Похоже, что отладчик сбрасывает с двух сторон, чтобы плавать где-то.

Ответ 9

Если вы используете Java и вам нужна точность, используйте класс BigDecimal для вычислений с плавающей запятой. Это медленнее, но безопаснее.

Ответ 10

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

Но большую часть времени вы можете просто игнорировать его. 21.4 == 21.4 по-прежнему будет истинным, потому что он по-прежнему совпадает с той же ошибкой. Но 21.4f == 21.4 может быть неверным, поскольку ошибка для float и double различна.

Если вам нужна фиксированная точность, возможно, вам стоит попробовать фиксированные номера точек. Или даже целые числа. Я, например, часто использую int (1000 * x) для передачи на debug пейджер.

Ответ 14

Согласно javadoc

"Если хотя бы один из операндов для числового оператора имеет тип double, то   операция выполняется с использованием 64-битной арифметики с плавающей запятой, а результат   числовым оператором является значение типа double. Если другой операнд не является двойным, это   сначала расширился (§5.1.5), чтобы ввести двойное числовое продвижение по службе (§5.6).

Вот источник