Еще один трюк двойного типа в C ++? - PullRequest
0 голосов
/ 19 марта 2012

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

Упрощенная версия моего вопроса: Я получил три разных результата (a=-0.926909, a=-0.926947 и a=-0.926862), когда я вычислил a=b-c+d с тремя разными подходами и одинаковыми значениями b, c и d, и я не знаю, какому из них доверять.

Подробная версия моего вопроса::

Недавно я писал программу (на C ++ в Ubuntu 10.10) для обработки некоторых данных.Одна функция выглядит так:

void calc() {
   double a, b;
   ...
   a = b - c + d; // c, d are global variables of double
   ...
}

Когда я использовал GDB для отладки вышеуказанного кода, во время вызова calc () я записал значения b, c и dперед оператором a = b - c + d следующим образом:

b = 54.7231
c = 55.4051
d = -0.244947

После того, как заявление a = b - c + d оправдано, я обнаружил, что a=-0.926909 вместо -0.926947, который рассчитывается калькулятором.Ну, пока это не совсем запутанно, так как я думаю, что это может быть просто проблемой точности.Позже я по какой-то причине повторно внедрил другую версию calc().Давайте назовем эту новую версию calc_new().calc_new() почти совпадает с calc(), за исключением того, как и где вычисляются b, c и d:

void calc_new() {
   double a, b;
   ...
   a = b - c + d; // c, d are global variables of double
   ...
}

На этот раз, когда я выполнял отладку, значенияb, c и d перед оператором a = b - c + d такие же, как при отладке calc(): b = 54.7231, c = 55.4051, d = -0.244947.Однако на этот раз после выполнения оператора a = b - c + d я получил a=-0.926862.При этом я получил три разных a, когда вычислил a = b - c + d с одинаковыми значениями b, c и d.Я думаю, что различия между a=-0.926862, a=-0.926909 и a=-0.926947 невелики, но я не могу понять причину.И какой из них правильный?

С большим спасибо, Том

Ответы [ 5 ]

2 голосов
/ 19 марта 2012

Если вы ожидаете, что ответ будет точным в 5-м и 6-м десятичных знаках, вам необходимо точно знать, какие входные данные для расчета находятся в этих местах.Вы видите входные данные только с 4 десятичными разрядами, вам также нужно отобразить их 5 и 6 места.Тогда я думаю, что вы увидите понятную ситуацию, которая соответствует вашему калькулятору до 6 знаков после запятой.У Double есть более чем достаточная точность для этой работы, здесь возникнут проблемы с точностью, если вы берете разницу двух очень похожих чисел (а вы нет).

Редактировать: Неудивительно, что повышение точности отображениятакже показали, что calc () и calc_new () предоставляли различные входные данные для расчета.Благодарим Майка Сеймура и Дитмара Куля в комментариях, которые первыми увидели вашу актуальную проблему.

1 голос
/ 19 марта 2012

Правильный -0,926947.

Различия, которые вы видите, слишком велики для ошибок округления (даже с одинарной точностью), так как можно зарегистрировать этот кодировщик . При использовании кодировщика вам необходимо ввести их следующим образом: -55.926909 (чтобы учесть потенциальный эффект эффектов коммутативности оператора, хорошо описанных в ранее представленных ответах.) Кроме того, различие только в последнем значащем бите вполне может быть связано с эффекты округления, но вы не увидите ничего с вашими значениями.

При использовании инструмента 64-битный формат (Binary64) соответствует двойному типу вашей реализации.

1 голос
/ 19 марта 2012

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

a = b + c + d

ДОЛЖЕН ВСЕГДА ВЕРНУТЬ ТОТ ЖЕ ЗНАЧЕНИЕ, ЕСЛИ ВХОДЫ НЕ ИЗМЕНИЛИСЬ .

Нет ошибок округления. Никаких эзотерических прагм, вообще ничего.

Если вы проверяете свой банковский счет сегодня и завтра (и ничего не изменилось за это время), я подозреваю, что вы сойдете с ума, если увидите что-то другое. Мы говорим о программах, а не о генераторах случайных чисел !!!

1 голос
/ 19 марта 2012

Позвольте мне попытаться ответить на вопрос, который я подозреваю, что вы хотели задать. Если я ошибаюсь в ваших намерениях, вы можете игнорировать ответ.

Предположим, у меня есть числа u = 500,1 и v = 5,001, каждое с точностью до четвертого знака после запятой. Что же тогда w = u + v ? Ответ: w = 505,101, , но с четырьмя десятичными знаками это w = 505,1.

Теперь рассмотрим x = w - u = 5.000, , который должен равняться v, , но не совсем.

Если я изменю только порядок операций, я получу x равным v точно, но не x = w - u или x = (u + v) - u, , но x = v + (u - u).

Это тривиально? Да, в моем примере это так; но тот же принцип применим в вашем примере, за исключением того, что они на самом деле не являются десятичными разрядами, а немного точны.

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

0 голосов
/ 19 марта 2012

Рациональные числа не всегда имеют конечное расширение в заданной базе.1/3 не может быть выражено конечным числом цифр в базовой десятке.В базе 2 рациональные числа со знаменателем, который является степенью двойки, будут иметь конечное расширение.Остальные не будут.Таким образом, 1/2, 1/4, 3/8, 7/16 .... любое число, которое выглядит как x / (2 ^ n), может быть представлено точно.Это оказывается довольно редким подмножеством бесконечного ряда рациональных чисел.Все остальное будет подвержено ошибкам, возникающим при попытке представить бесконечное число двоичных цифр в конечном контейнере.

Но сложение коммутативно, верно?Да.Но когда вы начинаете вводить ошибки округления, все немного меняется.На примере a = b + c + d предположим, что d нельзя выразить конечным числом двоичных цифр.Ни один не может с.Таким образом, сложение их вместе даст нам некоторое неточное значение, которое само по себе также может оказаться неспособным быть представленным в конечном количестве двоичных цифр.Так что ошибка на вершине ошибки.Затем мы добавляем это значение к b, которое также не может быть завершающим расширением в двоичном виде.Таким образом, взятие одного неточного результата и добавление его к другому неточному числу приводит к другому неточному числу.И поскольку мы отбрасываем точность на каждом шаге, мы потенциально нарушаем симметрию коммутативности на каждом шаге.

Я сделал пост: (связанный с Perl, но это универсальная тема) Re: Шокирующая неточность (PerlMonks) и, конечно, каноническое , что должен знать каждый учёный-компьютерщик по математике с плавающей запятой , которые обсуждают эту тему.Последнее гораздо более подробно.

...