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