2009-06-06 2 views
19

В моем курсе, я сказал:Ошибки округления?

Непрерывные значения представлены примерно в памяти, и, следовательно, вычисления с плавающей точкой включает в себя ошибки округления. Это крошечные несоответствия в битовых шаблонах; таким образом, тест e==f является небезопасным, если e и f являются поплавками.

Ссылаясь на Java.

Это правда? Я использовал сравнения с double и float и не имел проблем округления. Никогда не читал в учебнике что-то подобное. Наверняка виртуальная машина объясняет это?

ответ

43

Это правда.

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

Эта программа, например, печатает «ложные»:

public class Main { 
    public static void main(String[] args) { 
    double a = 0.7; 
    double b = 0.9; 
    double x = a + 0.1; 
    double y = b - 0.1; 
    System.out.println(x == y); 
    } 
} 

Вместо точного сравнения с «==» вы обычно решаете на каком-то уровне точности и спросить, если цифры «достаточно близко»:

System.out.println(Math.abs(x - y) < 0.0001); 
+2

Хорошее объяснение. Однако ваш последний пример кода должен, вероятно, использовать Math.abs (x - y) вместо x - y. – markusk

+0

Вы правы. Исправленный. –

+0

Из-за интуитивно понятного кода, это мой ответ выбора. Хороший! –

4

Это всегда так. Есть некоторые числа, которые невозможно точно представить с использованием представления точки с плавающей точкой. Рассмотрим, например, pi. Как бы вы представляли число, которое имеет бесконечные цифры, в конечном хранилище? Поэтому, сравнивая числа, вы должны проверить, меньше ли разница между ними, чем некоторый эпсилон. Кроме того, существует несколько классов, которые могут помочь вам достичь большей точности, такой как BigDecimal и BigInteger.

+1

+1 для BigDecimal – dfa

+0

да, но если два вычисления дают одно и то же число с плавающей запятой, использование e == f вернет true? –

+1

@Beau, если e и f действительно одинаковое число, проверка возвращает true. Но есть некоторые предостережения, например, казалось бы, простое и математически истинное сравнение, подобное ((x * y)/y == x) может быть ложным. –

21

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

Более подробную информацию о значениях с плавающей запятой:

What Every Computer Scientist Should Know About Floating-Point Arithmetic

+0

+1 - потрясающая ссылка. – duffymo

+3

Одно очко: если два вычисления точно совпадают, то их результирующие значения также будут одинаковыми. Проблема возникает, когда два вычисления математически эквивалентны, но различны. – dfrankow

2

Да, как и другие ответы сказали. Я хочу добавить, что я рекомендую вам эту статью о точности с плавающей запятой: Visualizing floats

7

Да, что составляет 0,1 точно в базе-2 такая же, как пытается представить 1/3 точно в базе 10.

3

Правильно. Обратите внимание, что Java не имеет к этому никакого отношения, проблема присуща математике с плавающей запятой в языке ANY.

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

Случай с давних времен в школе. Преподаватель класса intro задал проблему окончательного экзамена, которая доказывала настоящую doozy для многих из лучших учеников - она ​​не работала, и они не знали почему. (Я видел это как лаборант, меня не было в классе.) Наконец некоторые начали просить меня о помощи, и некоторые исследования выявили проблему: им никогда не учили о неотъемлемой неточности математики с плавающей запятой.

Теперь были две основные подходы к этой проблеме: грубая сила (которая случайно работала в этом случае, когда она делала одни и те же ошибки каждый раз) и более элегантная (что делало бы разные ошибки и не работало .) Любой, кто попробовал элегантный подход, ударил бы по кирпичной стене, не зная почему. Я помог им в кучу и застрял в комментарии, объяснив, почему и связаться со мной, если у него есть вопросы.

Конечно в следующем семестре я слышу от него об этом, и я в основном сражен всем отделе с простенькой программой:

10 X = 3000000 
20 X = X + 1 
30 If X < X + 1 goto 20 
40 Print "X = X + 1" 

Несмотря на то, что каждый преподаватель в отделе думал, это БУДЕТ прекратить. 3 миллиона семян - это просто ускорить его. (Если вы не знаете основную информацию: здесь нет трюков, просто изматывают точность чисел с плавающей запятой.)

1

Конечно, это правда. Думаю об этом. Любое число должно быть представлено в двоичном формате.

Картина: «1000» как 0,5 или 1/2, то есть 2 ** -1. Затем «0100» составляет 0,25 или 1/4. Вы можете видеть, куда я иду.

Сколько чисел вы можете представить таким образом? 2 ** 4. Добавление большего количества бит дублирует доступное пространство, но оно никогда не бывает бесконечным. 1/3 или 1/10, для вопроса 1/n любое число, не кратное 2, не может быть действительно представлено.

1/3 может быть «0101» (0,3125) или «0110» (0,375). Любое значение, если умножить его на 3, не будет 1. Конечно, вы можете добавить специальные правила. Скажите, что «когда вы добавляете 3 раза« 0101 », сделайте это« 1 »... этот подход не будет работать в конечном итоге. Вы можете поймать кого-то, но тогда как примерно 1/6 раза 2?

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

1

Большинство процессоров (и компьютерных языков) используют арифметику IEEE 754 с плавающей запятой. Используя это обозначение, есть десятичные числа, которые не имеют точного представления в этих обозначениях, например. 0,1. Поэтому, если вы разделите 1 на 10, вы не получите точный результат. При выполнении нескольких вычислений в строке ошибки суммируются. Попробуйте следующий пример в python:

>>> 0.1 
0.10000000000000001 
>>> 0.1/7 * 10 * 7 == 1 
False 

Это не то, что вы ожидаете математически.

К слову: Общее недоразумение в отношении чисел с плавающей запятой состоит в том, что результаты не являются точными и не могут быть безопасными. Это справедливо только в том случае, если вы действительно используете доли чисел. Если вся ваша математика находится в целочисленном домене, удваивает и плавает, делает то же самое, что и ints, а также может сравниваться безопасно. Например, их можно безопасно использовать в качестве счетчиков циклов.

+0

Я не согласен с вашими комментариями о float в целочисленном домене. Простой пример 1: \t \t \t float f2 = 20000000; \t \t \t, если (f2 == ++ f2) \t \t \t { \t \t \t \t; // упс \t \t \t} Пример 2: его полностью неочевидный, когда этот цикл будет прекращен: поплавка е = 0; \t \t \t \t \t в то время (правда) \t \t \t { \t \t \t \t если (== е ++ е) \t \t \t \t { \t \t \t \t \t перерыв; \t \t \t \t} \t \t \t} –

+1

Они могут быть безопасно по сравнению с тем, как долго вы остаетесь в диапазоне, где они могут быть представлены в виде целых чисел. См. Мое сообщение для примера, где это не удается. –

+0

@Ben: Пребывание в диапазоне - проблема с ints тоже: int i = 0; while (i <2147483648) {} Но я согласен, что есть более тонкие проблемы с переполненными поплавками. –

Смежные вопросы