Почему я вижу двойную переменную, инициализированную некоторым значением, например 21.4, как 21.399999618530273?
double r = 11.631; double theta = 21.4;
В отладчике они показаны как 11.631000000000000
и 21.399999618530273
.
Как я могу избежать этого?
- Быстрый метод для округления двойного до 32-битного int
- Преобразование float в double без потери точности
- Как определить число с плавающей запятой, используя регулярное выражение
- Алгоритм преобразования двоичного кода IEEE 754 в строку?
- Операции с плавающей запятой в C-ассоциативном?
- Почему Double.MIN_VALUE не является отрицательным?
- Android Как реализовать нижний лист из документации по материальному дизайну
- Какие типы чисел представляются в двоичной с плавающей запятой?
- Странная проблема сравнения поплавков в объективе-C
- Проблемы сравнения с плавающей запятой MySQL
- Манипулирование и сравнение плавающих точек в java
- Какой тип данных MySQL следует использовать для широты / долготы с 8 десятичными знаками?
- Почему Math.round (0.49999999999999994) возвращает 1?
Эти проблемы точности связаны с внутренним представлением чисел с плавающей запятой, и вы не можете сделать это, чтобы избежать этого.
Кстати, печать этих значений во время выполнения часто по-прежнему приводит к правильным результатам, по крайней мере, с использованием современных компиляторов C ++. Для большинства операций это не большая проблема.
Мне понравилось объяснение Джоэла , в котором рассматривается аналогичная проблема с двойной точкой с плавающей точкой в Excel 2007:
Посмотрите, как там много конца 0110 0110 0110? Это потому, что 0.1 не имеет точного представления в двоичном формате … это повторяющееся двоичное число. Это похоже на то, как 1/3 не имеет представления в десятичной форме. 1/3 – 0.33333333, и вы должны продолжать писать 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 существует class C #, который позволяет вам видеть абсолютно точное число, хранящееся в двойном. В настоящий момент онлайн-версия не работает – я постараюсь поставить ее на другой сайт.
Учитывая, что отладчик видит «фактическое» значение, он должен принять решение о том, что показывать – он может показать вам округленное значение до нескольких десятичных знаков или более точное значение. Некоторые отладчики выполняют лучшую работу, чем другие, при чтении умов разработчиков, но это фундаментальная проблема с двоичными числами с плавающей запятой.
Используйте 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 × 10 ~ -1
Когда на самом деле они находятся в базе-2
1.1 – 154811237190861 x 2 ^ -47
Вы не можете этого избежать, вам просто нужно привыкнуть к тому, что некоторые float являются «иррациональными», точно так же, как и 1/3.
Один из способов избежать этого – использовать библиотеку, которая использует альтернативный метод представления десятичных чисел, таких как BCD
Мне кажется, что 21.399999618530273 – это представление с единственной точностью (float) 21.4. Похоже, что отладчик сбрасывает с двойного значения, чтобы плавать где-то.
Если вы используете Java и вам нужна точность, используйте class BigDecimal для вычислений с плавающей запятой. Это медленнее, но безопаснее.
Вы не можете избежать этого, поскольку используете числа с плавающей запятой с фиксированным количеством байтов. Существует просто изоморфизм между действительными числами и его ограниченным обозначением.
Но большую часть времени вы можете просто игнорировать его. 21.4 == 21.4 по-прежнему будет истинным, потому что он по-прежнему совпадает с той же ошибкой. Но 21.4f == 21.4 может быть неверным, поскольку ошибка для float и double различна.
Если вам нужна фиксированная точность, возможно, вам стоит попробовать фиксированные номера точек. Или даже целые числа. Я, например, часто использую int (1000 * x) для передачи в debug пейджер.
Опасности компьютерной арифметики
Если это вас беспокоит, вы можете настроить способ отображения некоторых значений во время отладки. Используйте его с осторожностью 🙂
Улучшение отладки с атрибутами отображения отладчика
См. Общую десятичную арифметику
Также обратите внимание при сравнении поплавков, см. Этот ответ для получения дополнительной информации.
Согласно javadoc
«Если хотя бы один из операндов для числового оператора имеет тип double, то
операция выполняется с использованием 64-разрядной арифметики с плавающей запятой, а результат
числовым оператором является значение типа double. Если другой операнд не является двойным, это
сначала расширился (§5.1.5), чтобы ввести двойное числовое продвижение (§5.6). ”
Вот источник