double r = 11.631;
double theta = 21.4;
В отладчике они отображаются как 11.631000000000000
и 21.399999618530273
.
Как я могу избежать этого?
double r = 11.631;
double theta = 21.4;
В отладчике они отображаются как 11.631000000000000
и 21.399999618530273
.
Как я могу избежать этого?
Эти проблемы возникают из-за внутреннего представления чисел с плавающей запятой, и вы не можете сделать этого, чтобы избежать этого.
Кстати, печать этих значений во время выполнения часто по-прежнему приводит к правильным результатам, по крайней мере, с использованием современных компиляторов С++. Для большинства операций это не большая проблема.
Мне понравилось Объяснение Joel, в котором рассматривается аналогичная проблема с двоичной точностью с плавающей точкой в Excel 2007:
Посмотрите, как там много конца 0110 0110 0110? Это потому, что 0,1 не имеет точного представления в двоичном формате... это повторяющееся двоичное число. Это похоже на то, как 1/3 не имеет представления в десятичной форме. 1/3 - 0,333333333, и вы должны продолжать писать 3 навсегда. Если вы потеряете терпение, вы получите что-то неточное.
Итак, вы можете себе представить, как в десятичном случае, если вы попытались сделать 3 * 1/3, и у вас не было времени писать 3 навсегда, результат, который вы получите, будет 0.99999999, а не 1, и люди злитесь на вас за то, что вы ошибаетесь.
Если у вас есть значение, например:
double theta = 21.4;
И вы хотите сделать:
if (theta == 21.4)
{
}
Вы должны быть немного умны, вам нужно будет проверить, действительно ли значение тета действительно близко к 21.4, но не обязательно это значение.
if (fabs(theta - 21.4) <= 1e-6)
{
}
Это частично зависит от платформы - и мы не знаем, какую платформу вы используете.
Это также частично случай, когда вы знаете, что вы действительно хотите видеть. Отладчик покажет вам - в какой-то степени, точное значение, сохраненное в вашей переменной. В статье о двоичных числах с плавающей запятой в .NET существует класс С#, который позволяет увидеть абсолютно точное число, сохраненное в двойном. Онлайн-версия сейчас не работает - я постараюсь поставить ее на другой сайт.
Учитывая, что отладчик видит "фактическое" значение, он должен принять решение о том, что показывать - он может показать вам округленное значение до нескольких десятичных знаков или более точное значение. Некоторые отладчики выполняют лучшую работу, чем другие, при чтении умов разработчиков, но это фундаментальная проблема с двоичными числами с плавающей запятой.
Используйте тип неподвижной точки decimal
, если вы хотите, чтобы стабильность была в пределах точности. Есть накладные расходы, и вы должны явно указать, хотите ли вы конвертировать в плавающую точку. Если вы конвертируете в плавающую точку, вы снова введете неустойчивости, которые, похоже, беспокоят вас.
В качестве альтернативы вы можете преодолеть это и научиться работать с ограниченной точностью арифметики с плавающей запятой. Например, вы можете использовать округление для сближения значений, или вы можете использовать сравнения epsilon для описания допусков. "Эпсилон" - это константа, которую вы настраиваете, которая определяет допуск. Например, вы можете считать, что два значения равны, если они находятся в пределах 0,0001 друг от друга.
Мне кажется, что вы можете использовать перегрузку оператора, чтобы сделать сравнения epsilon прозрачными. Это было бы очень круто.
Для представлений мантиссы-экспоненты EPSILON необходимо вычислить, чтобы оставаться в пределах отображаемой точности. Для числа N Epsilon = N/10E + 14
System.Double.Epsilon
- наименьшее представимое положительное значение для типа Double
. Это слишком мало для нашей цели. Прочитайте Совет Microsoft по тестированию на равенство
Я столкнулся с этим раньше (в моем блоге) - Я думаю, что сюрприз, как правило, заключается в том, что "иррациональные" номера отличается.
Под "иррациональным" здесь я просто ссылаюсь на то, что они не могут быть точно представлены в этом формате. Реальные иррациональные числа (например, π - pi) не могут быть точно представлены вообще.
Большинство людей знакомы с тем, что 1/3 не работает в десятичной системе: 0.3333333333333...
Странно, что 1.1 не работает в поплавках. Люди ожидают, что десятичные значения будут работать в числах с плавающей запятой из-за того, как они думают о них:
1,1 составляет 11 x 10 ^ -1
Когда они на самом деле находятся в базе-2
1.1 равен 154811237190861 x 2 ^ -47
Вы не можете этого избежать, вам просто нужно привыкнуть к тому, что некоторые float являются "иррациональными", так же, как и 1/3.
Один из способов избежать этого - использовать библиотеку, которая использует альтернативный метод представления десятичных чисел, например BCD
Мне кажется, что 21.399999618530273 является представлением одинарной точности (float) 21.4. Похоже, что отладчик сбрасывает с двух сторон, чтобы плавать где-то.
Если вы используете Java и вам нужна точность, используйте класс BigDecimal для вычислений с плавающей запятой. Это медленнее, но безопаснее.
Вы не можете избежать этого, поскольку используете числа с плавающей запятой с фиксированным количеством байтов. Просто нет изоморфизма между действительными числами и его ограниченной нотации.
Но большую часть времени вы можете просто игнорировать его. 21.4 == 21.4 по-прежнему будет истинным, потому что он по-прежнему совпадает с той же ошибкой. Но 21.4f == 21.4 может быть неверным, поскольку ошибка для float и double различна.
Если вам нужна фиксированная точность, возможно, вам стоит попробовать фиксированные номера точек. Или даже целые числа. Я, например, часто использую int (1000 * x) для передачи на debug пейджер.
Если это вас беспокоит, вы можете настроить способ отображения некоторых значений во время отладки. Используйте его с осторожностью: -)
Обратитесь к Общая десятичная арифметика
Также обратите внимание при сравнении поплавков, см. этот ответ для получения дополнительной информации.
Согласно javadoc
"Если хотя бы один из операндов для числового оператора имеет тип double, то операция выполняется с использованием 64-битной арифметики с плавающей запятой, а результат числовым оператором является значение типа double. Если другой операнд не является двойным, это сначала расширился (§5.1.5), чтобы ввести двойное числовое продвижение по службе (§5.6).