2008-10-07 17 views

ответ

54

Данные accuracy problems связаны с числами с плавающей точкой internal representation, и вы не можете сделать это, чтобы избежать этого.

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

+0

Это что-то программисты должны знать, хотя, особенно если они работают с очень большими или очень малыми числами, где точность может иметь важное значение. – tloach 2008-10-07 09:56:23

+0

Не обязательно очень большой или очень маленький - точность с плавающей точкой одинакова независимо от общего размера номера. Проблема заключается в том, что вы * смешиваете * очень большие и очень маленькие значения, например, добавляя их вместе. – 2008-10-07 09:58:49

+4

Dark - на самом деле это не так. Пространство представляемых значений намного плотнее около 0 и гораздо более разреженное, когда вы выходите на бесконечность (например, 2^24 + 1 не может быть точно отображено с использованием стандарта с плавающей точкой IEEE для 32-битных удвоений) – SquareCog 2008-10-10 16:07:12

9

Если у вас есть значение, как:

double theta = 21.4; 

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

if (theta == 21.4) 
{ 
} 

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

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

double theta = 21.4; bool b = theta == 21.4; // здесь b всегда истинно – 2008-11-04 15:24:48

3

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

+0

Есть лучшие методы, чем BCD. – 2008-10-07 08:10:20

+1

Было бы хорошо сказать один или два из тех приемов. – 2008-10-07 09:32:52

+0

Извините: произвольная точность арифметики: см. Например, http://gmplib.org/. – 2008-10-07 19:40:35

2

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

+0

Вы имеете в виду * вместо * с плавающей запятой. – EJP 2014-07-21 09:29:41

36

мне понравилась Joel's explanation, которая занимается подобной двоичной плавающей точкой точности выдачи в Excel 2007:

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

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

5

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

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

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

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

System.Double.Epsilon - наименьшее представимое положительное значение для типа Double. Это тоже маленький для нашей цели. Read Microsoft's advice on equality testing

7

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

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

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

3

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

2

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

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

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

4

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

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

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

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

1.1 составляет 11 х 10^-1

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

1.1 154811237190861 х 2^-47

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

0

Согласно Javadoc

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

Here is the Source