Почему я вижу двойную переменную, инициализированную некоторым значением, таким как 21.4, как 21.399999618530273? - PullRequest
46 голосов
/ 07 октября 2008
double r = 11.631;
double theta = 21.4;

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

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

Ответы [ 14 ]

57 голосов
/ 07 октября 2008

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

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

38 голосов
/ 07 октября 2008

Мне понравилось Объяснение Джоэла , которое касается аналогичной проблемы точности двоичных чисел в Excel 2007:

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

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

10 голосов
/ 07 октября 2008

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

double theta = 21.4;

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

if (theta == 21.4)
{
}

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

if (fabs(theta - 21.4) <= 1e-6)
{
}
7 голосов
/ 07 октября 2008

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

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

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

5 голосов
/ 07 октября 2008

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

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

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


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

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

4 голосов
/ 07 октября 2008

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

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

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

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

1.1 - 11 x 10 ^ -1

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

1.1 - 154811237190861 x 2 ^ -47

Вы не можете избежать этого, вы просто должны привыкнуть к тому факту, что некоторые поплавки «иррациональны», так же, как 1/3.

3 голосов
/ 07 октября 2008

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

3 голосов
/ 07 октября 2008

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

2 голосов
/ 02 ноября 2008
2 голосов
/ 07 октября 2008

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

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

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

...