О плавающей точке

Что напечатает:
  unsigned short v=59259;
  float a=0.0f;
  double b=0.0;
  for(int i=0;i<33*33;i++)
  {
        a+=v;
        b+=v;
  }
  printf("%g\n",(a-b)/(33*33));

Вроде и значения все вполне небольшие, и число итераций сложения вполне человеческое, однако ж.

P.S. При особом везении тут можно и третий знак разницы получить, а не шестой....

Comments

unsigned short v=59259;
float a=0.0f;
double b=0.0;
char ahtung=' ';
for(int i=0;i<33*33;i++)
{
a+=v;
b+=v;
if (a!=b) ahtung='!';
cout << i << " " << a << " " << b << " " << ahtung << "\n";
}

cout << (a-b)/(33*33) << "a = " << a << "b = "<< b;
char r;
cin >> r;
return 0;
}

282 1.67703e+007 1.67703e+007
283 1.68296e+007 1.68296e+007
284 1.68888e+007 1.68888e+007 !
285 1.69481e+007 1.69481e+007 !
286 1.70073e+007 1.70073e+007 !
287 1.70666e+007 1.70666e+007 !

А можно объяснить для поручиков, зачем перемножать 33*33, если компилятор это соптимизирует?

Ну потому что мне лень было умножать в уме, а это реальная константа из реальной программы :)

У меня по плашке считается минимум-максимум (в целых), среднее (в плавучке) и стандартное отклонение (в плавучке же). При этом, отклонение считается правильно, в один проход (а не расчетом среднего и вычитанием вторым проходом).

Результат феноменальный:
минимум - 59259
максимум - 59259
дисперсия - 0
среднее - 59259.7

unsigned short v=59259;
float a=0.0f;
double b=0.0;
float averfa=0.0f;
double averdb=0.0;
char ahtung=' ';
for(int i=0;i<1089;i++)
{
if(i){ averfa=(v+averfa)/2.0;} else {averfa+=v;}
if(i){ averdb=(v+averdb)/2.0;} else {averdb+=v;}
a+=v;
b+=v;

if (a!=b) ahtung='!';
cout << i << " " << a << " " << b << " " << ahtung << " " << averfa << " " << averdb << "\n";
}

1087 6.44746e+007 6.44738e+007 ! 59259 59259
1088 6.45339e+007 6.45331e+007 ! 59259 59259
0.73921a = 6.45339e+007b = 6.45331e+007

Хмм? В один проход?

Неаккуратненько.
if(i){ averfa=(v+averfa)/2.0f;} else {averfa+=v;}
Сорри.

Я не понимаю вашего кода. Вот смотрите:
float avg=0.0f;
int i;
for(i=0;i<1000;i++)
{
float a = i;
if(i) { avg = (avg+a)/2.0f; } else { avg+=ф;}
}
printf("%g\n",avg);

Это же то же самое делает?

Но оно не среднее печатает, отнюдь.

ф читать как a
:)

Да, лажаю. :-(

Блин, второй час ночи.
Единственное, что удалось сделать, это из 7 десятых 2 десятых на 1000 сложений. На 20000 разница жуткая.
Расскажите, как выйдете из ситуации, если будет свободное время, пожалуйста.

Да просто перешел на double - и все.

Все чаще убеждаюсь, что все уже придумано до нас. :-)


main ()
{
unsigned short v=59259;
float a=0.0f;
double b=0.0;
// float divider=100000.0;
// float divider=50000.0f;
float divider=1.0;
int i;

float c = 0.0f; //A running compensation for lost low-order bits.
float t;
float y;
float sum = 0.0f;

for(i=0;i<33*33;i++)
{
a+=v/divider;
b+=v/divider;
}
a*=divider;
b*=divider;
printf("%.15g a=%.15g b=%.15g a-b=%.15g int=%d iter=%d\n",(a-b)/(33*33), a, b, a-b, v*33*33, 33*33 );

// Vsjo pridumano do nas
a=0.0f;
b=0.0;
for(i=0;i<33*33;i++)
{
// a+=v; // see below
y = v - c; //So far, so good: c is zero.
t = sum + y; //Alas, sum is big, y small, so low-order digits of y are lost.
c = (t - sum) - y; //(t - sum) recovers the high-order part of y; subtracting y recovers -(low part of y)
sum = t; //Algebraically, c should always be zero. Beware eagerly optimising compilers!
//Next time around, the lost low part will be added to y in a fresh attempt.
b+=v;
}
a = sum;
printf("%.15g a=%.15g b=%.15g a-b=%.15g int=%d iter=%d\n",(a-b)/(33*33), a, b, a-b, v*33*33, 33*33 );

}

0.73921028466483 a=64533856 b=64533051 a-b=805 int=64533051 iter=1089
0.000918273645546373 a=64533052 b=64533051 a-b=1 int=64533051 iter=1089

Правда операций чуть побольше.

В вариации float/double (у обоих есть аппаратная поддержка) - это бессмысленно.

в с мы не можем спасти больше битов, чем их есть в мантиссе. Т.е. просто переход на double в аккумуляторе - делает не хуже, а операций - вчетверо меньше. Т.е. double-решение будет вдвое быстрее.

Но если не хватает double, то да, спасет.

Скока той мантиссы? Если мне не изменяет память 24 бита. А скока их в а? 26? И ошибка в единичку (можно и в ноль свести при последующих вычислениях). Шайтан однако. :-)
Кстати gcc -O отличное от ноль тоже интересно.

Но в общем все правильно.

Ну да, мантиссы - 24 бита (не помню, включает это знак или нет, но скорее всего 23 бита и знак). Это в float.
А в double - битов 48 или около того.

Но в float - 4 действия, а в double - одно, при этом double всего-то вдвое медленнее.

замечательно ;)

Ага.
И никаких тебе "вычитаний близких чисел", делений и тому подобного, на чем обычно ошибки накапливаются. Не говоря про переполнение.

Просто "случайное совпадение", в том смысле, что мы маленькое (в 7-8-м знаке) отклонение взяли и просуммировали 1089 раз.

и double - не панацея (хотя конечно с double практически нереально наткнуться на что-то подобное)

С даблом то же самое (по смыслу), только для получения эффекта того же порядка нужно не 1000 итераций в цикле, а 10^10.

А я тут давеча слышал вопль души по направлению к Интел и АМД "ну когда же в процессорах будет поддержка четверной точности". Не хватает кое-кому уже double.

У нас на факультете геофизики очень любили (и, возможно, до сих пор любят) Sun-ы за 128-битный float.

И, кстати, кайф в том, что классический расчет стандартного отклонения (сначала среднее, потом дельты возводим в квадрат и суммируем) - даст неверный результат.

Кстати, к вопросу о float. Начал изучать libraw, так он всё-таки не на float работает, да? То есть, в коде явно

ushort (*image)[4] ;

Основной объем работы - в целых. Что-то там внутрях - в плавучке.

Но постпроцессинг LibRaw - не перестаю это повторять - он не настоящий, это муляж такой, чтобы было.

В смысле, не настоящий?

А, то есть основная идея, что постпроцессинг должен делать уже человек в своём приложении?

Да, целеполагание у LibRaw - это снять у прикладного программиста головную боль про 100 форматов и 400 камер.
То есть и постпроцессинг и даже вычитание черного - сам, все сам.

К несчастью, даже при таком уровне абстракции это трудно, обязательно всплывут обстоятельства (вроде Fuji с кривым aspect ratio и двумя кадрами), но всяко легче, чем без LibRaw.

Иначе нет смысла, чем один raw-процессор будет отличаться от другого, цветом рамочки вокруг light table?

Я думал, что libraw, в перспективе, и претендует стать самостоятельным raw процессором. В принципе, оно уже сейчас им является, использовать вполне можно.

Просто как. Какие-то проблемы более эффективно решать на этапе до демозаики. Например, тот же debanding. Или, например, кто-то промеряет линейность ЦАП, и выдаст кривую. Какие-то - ещё до применения цветового профиля. Например - восстановление highlight clipping, или виньетки.

Какие-то, может, вообще по-хорошему нужно интегрировать в процесс демозаики. Например, подавление хроматических аберраций. Сейчас, да, это из области фантастики, да. Но в будущем у человечества, может, и до этого руки дойдут :-)

А если алгоритм шумоподавления захочет снять статданные, например, со служебной рамки изображения? Или кто-то захочет написать свою, расово правильную, систему цветокоррекции?

Ну, не говоря уже о том, что да. Появляются всякие fujy CCD, foveon, и другие. И, в конечном итоге, для них нужен вообще свой тип постпроцессинга.

Короче, я почему-то видел это как некую основу, уже куда можно интегрировать свои модули. Причём на всех этапах. А при желании, порядок этапов тоже менять.

Я думал, что libraw, в перспективе, и претендует стать самостоятельным raw процессором.

Нет-нет. Эту тему мы обсуждали в самом начале и решили что нет, не претендуем. RAW-процессор *как библиотека* (т.е. приделывай свой GUI и радуйся) это какая-то неправильная штука. Или она будет слишком generic (и потому - медленной), либо там нужна какая-то строгая идеология процессинга, а тогда и GUI можно приделать свой (и исходники открывать необязательно).

Нет, этот самый постпроцессинг достался на халяву из dcraw, немножко разросся в последние полгода, но сильно развивать это место (на сегодняшний день) не планируется.

Иманна-иманна...

Вот тут или на худой конец тут как гриццо совершенно famous article на этот счёт

Теорию я, типа, знаю.

Но практика смешная: исходное 16-битное число в 23-битную мантиссу лезет точно (во всяком случае, обратно печатается как надо, побитовое представление я не проверял). Итераций ~2^10 т.е. должно накопиться бита три ошибки. А накапливается - битов шесть.

Раскладец.

Хех, люблю я хитрые задачки)
Делаем вот что:

float v = ((unsigned short)59259);
printf("v=%0.023f\n",v);
float a=0.0f;
double b=0.0;
bool bl=false;
for(int i=0;i<33*33;i++){
a+=v;
b+=v;
if (283==i) printf("a=%0.023f, b=%0.023f, i=%d\n",a,b,i);
if (a!=b && !bl){
bl=true;
printf("a=%0.023f, b=%0.023f, i=%d\n",a,b,i);
}
}
printf("%g\n",(a-b)/(33*33));

И получаем вывод такой:

v=59259.00000000000000000000000
a=16829556.00000000000000000000000, b=16829556.00000000000000000000000, i=283
a=16888816.00000000000000000000000, b=16888815.00000000000000000000000, i=284
0.73921

Т.е. прибавление фиксированного числа на 284 итерации прибавляет неправильно. Присмотревшись к диапазону чисел замечаем, что это однако близко к 0x1000000, то бишь 2^24, а мантисса у флоата как раз такая же. Далее дорогой гугл приводит нас на не менее дорогой StackOverflow, так же не менее дорого Джоэла Спольски, а уже там нам старшие товарищи подсказывают вот что:

In single precision floating point there are only 24 bits of precision available, and 2^24 is 16777216. There is no way to encode 16777217 in 24 bits, so it simply stays at 16777216, on the reasoning that it's close enough to the real answer. The real problem arises when you add lots of very small numbers to a big number, where the sum of the small numbers is signifiant relative to the big one, but individually they are not.

Note that 16777216.0f is not the largest number that can be represented in float, but just represents the limit of precision. For example, 16777216.0f x 2^4 + 2^4 => 16777216.0f x 2^4

double has 53 bits of precision, so can encode up to 2^53, or 9007199254740992.0 before hitting the point where adding 1.0d fails.

Ну, в общем, я тоже не допёр сам, хотя мог бы... Получается это как раз тот случай, когда "лучше сладко проспать утро понедельника и выспаться, чем провести всю неделю отлаживая код, написанный в понедельник утром" ;)))

Спасибо за полезную задачку, мне с флоатингом тоже дел много иметь приходится, и я тоже немного знаю теорию, но вот практика - да, практика всё, а пока с такими вещами похоже не сталкивался)

У меня это место подетектилось только когда я в варез загнал заранее известные *константные* значение (то самое 59259).

Так как там минимум-максимум-среднее выводятся рядом, то вот оно и вылезло.

практикуют с этой целью маньякусы.
Т.е. из 2**К чисел получают 2**(К-1) промежуточных сумм (попарных) итд итп ипкнх.

немного не по теме:
http://en.wikipedia.org/wiki/Kahan_summation_algorithm