Точность расчета тестовой функции Розенброка

0

Я хочу рассчитать функцию теста Rosenbrock

Изображение 174551

Я реализовал следующий код C/C++

#include <stdio.h>

/********/
/* MAIN */
/********/
int main()
{
    const int N = 900000;

    float *x = (float *)malloc(N * sizeof(float));
    for (int i=0; i<N; i++) x[i] = 3.f;

    float sum_host = 0.f;
    for (int i=0; i<N-1; i++) {
        float temp = (100.f * (x[i+1] - x[i] * x[i]) * (x[i+1] - x[i] * x[i]) + (x[i] - 1.f) * (x[i] - 1.f));
        sum_host = sum_host + temp;
        printf("%i %f %f\n", i, temp, sum_host);
    }
    printf("Result for Rosenbrock test function calculation = %f\n", sum_host);

}

Поскольку массив x инициализируется на 3.f, тогда каждый член суммирования должен быть 3604.f, так что окончательное суммирование с 899999 членами должно быть 3243596396. Однако я получаю 3229239296 с абсолютной ошибкой 14357100. Если я измеряю разницу между двумя последовательными частичными суммами, я вижу, что для ранних парциальных суммирования это 3600.f а затем он падает до 3584 для последних, тогда как он всегда должен быть 3604.f

Если я использую алгоритм суммирования Кахана как

sum_host = 0.f;
float c        = 0.f;
for (int i=0; i<N-1; i++) {
    float temp = (100.f * (x[i+1] - x[i] * x[i]) * (x[i+1] - x[i] * x[i]) + (x[i] - 1.f) * (x[i] - 1.f)) - c;
    float t    = sum_host + temp;
    c          = (t - sum_host) - temp;
    sum_host = t;
}

результатом я получаю 3243596288, с гораздо меньшей абсолютной ошибкой 108.

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

  • 0
    Для меня он начинается с 3604, а не с 3600. Ты сделал опечатку?
  • 0
    @tmyklebu Это значение ( 3600 ) относится не к самой первой конечной разнице между последовательными частичными суммированиями, а к (приблизительно) 26000 й разнице.
Показать ещё 4 комментария
Теги:
floating-point
floating-accuracy

2 ответа

4
Лучший ответ

Вы точно вычисляете temp = 3604.0f на каждой итерации. Проблема возникает, когда вы пытаетесь добавить 3604.0f к чему-то другому и округлить результат до ближайшего float. float хранит экспоненту и 23-битную значимость, что означает, что любой результат с 1 битом более 24 мест будет округлен до чего-то другого, кроме того, что он есть.

Заметим, что 3604 = 901 * 4 и двоичное расширение 901 равно 1110000101; вы начнете видеть округление, как только вы начнете добавлять temp к чему-то большему, чем 2 ^ 24 * 4 = 67108864. (Это происходит и при запуске кода, оно начинает печатать 3600 как разницу между последовательным sum_host справа, когда sum_host превышает 67108864.) Вы начинаете видеть еще больше округления, когда добавляете temp к чему-то большему, чем 2 ^ 26 * 4; в этот момент второй наименьший "1" бит тоже проглатывается.

Обратите внимание, что после того, как вы суммируете Kahan, sum_host - это то, что вы сообщаете, а c - -108. Это слабо, потому что c отслеживает следующие наиболее важные 24 бита.

  • 0
    Это «слабо» напоминает мне, что у меня есть к вам вопрос.
  • 0
    @PascalCuoq: Э-э-э Это невероятно сложно, случайно?
Показать ещё 1 комментарий
2

Типичный float хорош только для 7 цифр точности. Неоднократно добавив 3604 к номеру 100000x больше, чем он не аккумулирует меньшие значащие цифры.

Используйте double.

  • 0
    Я понизил голосование, потому что я не думаю, что ваш ответ затрагивает вопрос в какой-либо степени. Downvotes являются обратимыми; если вы улучшите свой ответ, я с удовольствием превращу понижающее голосование в положительное.
  • 0
    @tmyklebu Я отправил, вы отправили 10 секунд спустя, вы проголосовали 3 секунды спустя. Хммм. Этот ответ является кратким и правильным. Это достаточно глубоко, чтобы объяснить проблему и предлагает решение.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню