Я хочу рассчитать функцию теста Rosenbrock
Я реализовал следующий код 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
.
Я уверен, что этот эффект, который я вижу, следует отнести к точности арифметики с плавающей запятой. Может ли кто-нибудь подтвердить это и дать мне объяснение механизма, в соответствии с которым это происходит?
Вы точно вычисляете 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 бита.
Типичный float
хорош только для 7 цифр точности. Неоднократно добавив 3604
к номеру 100000x больше, чем он не аккумулирует меньшие значащие цифры.
Используйте double
.
3600
) относится не к самой первой конечной разнице между последовательными частичными суммированиями, а к (приблизительно)26000
й разнице.