Комментарии 22
Многие программисты об этом забывают по тому, что арифметика с плавающей точкой встречается на порядок реже, чем целочисленная. Да и ошибки здесь на порядок веселее. Вспомнился такой код:
double d = 0;
for(int i=0;i<1000;i++) d += 0.2;
bool eq = (d == 200);
Упс, ошибся, там d+=0,1 и d==100
Дело в том, что такие ошибки допускают люди, которые по несколько лет почти ежедневно имеют дело с плавающей точкой. Вектора, матрицы, кватернионы каждый день, а потом раз и такая ошибка в коде. А им ведь деньги за этот код платят.
Большинство программистов даже зная как устроен дабл будут допускать подобные ошибки. И проблема совсем в другом — в понимании. А дабл нужно понимать как value + eps, где value то что вы хотели представить как double, а eps — относительная погрешность округления (машинное Эпсилон). Если взять пример автора и провести анализ относительных погрешностей:
r (x1) = r (y1) = r (x2) = r (y2) = eps,
r (x1 * x1 + y1 * y1) = r (x2 * x2 + y2 * y2) = 4 * eps,
r (len1) = r (len2) = 3 * eps,
r (x1 / len1) = r (y1 / len1) = r (x2 / len2) = r (y2 / len2) = 5 * eps,
r (dotProduct) = 12 * eps, если брать x1 * x2 и y1 * y2 одного знака как в примере автора.
Вот теперь понятно откуда берутся значения больше 1.0. Вы думаете каждый программист без соответствующего мат. образования может выполнить такой анализ?
r (x1) = r (y1) = r (x2) = r (y2) = eps,
r (x1 * x1 + y1 * y1) = r (x2 * x2 + y2 * y2) = 4 * eps,
r (len1) = r (len2) = 3 * eps,
r (x1 / len1) = r (y1 / len1) = r (x2 / len2) = r (y2 / len2) = 5 * eps,
r (dotProduct) = 12 * eps, если брать x1 * x2 и y1 * y2 одного знака как в примере автора.
Вот теперь понятно откуда берутся значения больше 1.0. Вы думаете каждый программист без соответствующего мат. образования может выполнить такой анализ?
Надо считать точнее: r(x1*x1+y1*y1)=2*x1*r(x1)+2*y1*r(y1)+(x1^2+y1^2+(x1^2+y1^2))*eps = 2*(x1+y1+x1^2+y1^2)*eps. В итоге результат может оказаться совершенно другим. Например при извлечении корня могут появляться члены вроде eps^{1/2}. А это уже вполне чувствительная величина.
Минуточку x1 * x1 и y1 * y1 строго больше нуля. Поетому r(x1 * x1 + y1 * y1) = min(r(x1*x1), r(y1*y1)) + eps = min(r(x1) + r(x1) + eps, r(y1) + r(y1) + eps) + eps = min(3*eps, 3*eps) + eps = 4*eps.
Поправка вместо min должен быть max, но суть таже.
Дело в том, что погрешность в x1 увеличивается сама по себе даже при точном возведении в квадрат — (x1+eps)*(x1+eps)=x1^2+2*x*eps+eps*eps. К этому мы добавляем погрешность произведения (она относительна в виду природы строения действительных чисел) — x1*x1*eps. Членами eps^2 пренебрегаем.
PS На самом деле r(x1)=0, и погрешность получается меньше — 2*(x1^2+y1^2)*eps.
PS На самом деле r(x1)=0, и погрешность получается меньше — 2*(x1^2+y1^2)*eps.
Простите, но используйте тег
без него сложно читать...
В таком случае я не понимаю чем мой ответ отличается от ваших рассуждений. Относительная ошибка
r(x1 * x1) у вас тоже 3 * eps.
r(x1 * x1) у вас тоже 3 * eps.
Как вовремя статейка подвернулась! Я только что мучился с векторным 2d движком и как раз эта проблема была. Я топорно решил: умножал каждое число на 100 000, потом округлял, потом делил на 100 000. Ну тупо рубил количество знаков после запятой т.к. максимальная точность не нужна для этого игродвижка, как выяснилось.
Классика жанра, читать всем кто использует вычисления с плавающей точкой:
What Every Computer Scientist Should Know About Floating-Point Arithmetic
ЗЫ pdf гуглится
What Every Computer Scientist Should Know About Floating-Point Arithmetic
ЗЫ pdf гуглится
В первом примере надо сравнивать, делитель меньше FLT_MIN или нет? Прост смущает как что-то может быть меньше чем самое маленькое вещественное число.
Да, таким вещам надо учить с младенчества!
Это же язык для извращенцев, в нормальном языке программирования не должно быть запутанностей и нелогичностей, и штука вроде if (b != 0) { result = a /b } должна работать для абсолютно любых значений переменных, потому что программистам больше делать наверно нечего, как читать многостраничные мануалы по IEEE сколько-то там.
Если бы Марк Цукенберг занимался такой ерундой, фейсбук бы не появился.
Если бы Марк Цукенберг занимался такой ерундой, фейсбук бы не появился.
double и в Африке double. И если в вашем коде есть нечто вроде if (b! = 0) {result = a / b} это свидетельствует не о проблеме языка программирования, а о проблеме реализации.
Для b == 0, a / b == INF, что соответствует реальности, поэтому не вижу никаких неточностей со стороны языка программирования. Что вы называете тогда нормальным языком программирования?
Для b == 0, a / b == INF, что соответствует реальности, поэтому не вижу никаких неточностей со стороны языка программирования. Что вы называете тогда нормальным языком программирования?
Толсто, очень толсто.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Откуда берутся NaNы?