Я ищу подробную информацию о long double
и __float128
в GCC/x86 (больше из любопытства, чем из-за реальной проблемы).
Мало кто, возможно, когда-нибудь понадобится для этого (мне просто в первый раз действительно нужен был double
), но я думаю, что все же стоит (и интересно) знать, что у вас есть на панели инструментов и что это значит.
В этом свете, пожалуйста, извините мои несколько открытые вопросы:
- Может ли кто-нибудь объяснить обоснование реализации и предполагаемое использование этих типов, также в сравнении друг с другом? Например, являются ли они "реализациями смущения", потому что стандарт допускает тип, и кто-то может жаловаться, если они имеют только ту же точность, что и
double
, или они предназначены для первоклассных типов? - В качестве альтернативы, у кого-то есть хорошая, полезная веб-ссылка для совместного использования? Поиск Google в
"long double" site:gcc.gnu.org/onlinedocs
не дал мне много полезного. - Предполагая, что общая мантра "если вы считаете, что вам нужно удвоить, вы, вероятно, не понимаете, с плавающей точкой", не применяется, то есть вам действительно нужна более высокая точность, чем просто
float
, и все равно, 8 или 16 байт памяти сжигаются... разумно ли ожидать, что можно просто перейти наlong double
или__float128
вместоdouble
без значительного воздействия на производительность? - Функция расширенной точности процессоров Intel исторически была источником неприятных сюрпризов, когда значения перемещались между памятью и регистрами. Если на самом деле хранится 96 бит, тип
long double
должен устранить эту проблему. С другой стороны, я понимаю, что типlong double
является взаимоисключающим с-mfpmath=sse
, поскольку в SSE нет такой вещи, как "расширенная точность".__float128
, с другой стороны, должен отлично работать с математикой SSE (хотя в отсутствие инструкций с четкой точностью, конечно, не на базе инструкций 1:1). Правильно ли я в этих предположениях?
(3 и 4. возможно, можно понять с некоторой работой, потраченной на профилирование и разборку, но, возможно, кто-то еще думал об этом ранее и уже сделал эту работу.)
Фон (это часть TL; DR):
Я сначала наткнулся на long double
, потому что я искал DBL_MAX
в <float.h>
, а случайно LDBL_MAX
- на следующей строке. "О, посмотри, у GCC на самом деле есть 128 бит в два раза, а не то, что они мне нужны, но... круто" была моей первой мыслью. Сюрприз, сюрприз: sizeof(long double)
возвращает 12... подождите, вы имеете в виду 16?
Стандарты C и С++ неудивительно не дают очень конкретного определения типа. C99 (6.2.5 10) говорит, что числа double
являются подмножеством long double
, тогда как С++ 03 утверждает (3.9.1 8), что long double
имеет как минимум такую же точность, как double
(что это одно и то же, только по-разному). В принципе, стандарты оставляют все для реализации так же, как с long
, int
и short
.
В Википедии говорится, что GCC использует "80-битную расширенную точность для процессоров x86 независимо от используемого физического хранилища".
В документации GCC указано все на той же странице, что размер этого типа составляет 96 бит из-за i386 ABI, но не более 80 бит точности разрешены любой опцией (да? какой?), также Pentium и более новые процессоры хотят, чтобы они были выровнены как 128-битные числа. Это значение по умолчанию составляет 64 бит и может быть включено вручную под 32 битами, что приводит к 32-разрядному нулевому заполнению.
Время выполнения теста:
#include <stdio.h>
#include <cfloat>
int main()
{
#ifdef USE_FLOAT128
typedef __float128 long_double_t;
#else
typedef long double long_double_t;
#endif
long_double_t ld;
int* i = (int*) &ld;
i[0] = i[1] = i[2] = i[3] = 0xdeadbeef;
for(ld = 0.0000000000000001; ld < LDBL_MAX; ld *= 1.0000001)
printf("%08x-%08x-%08x-%08x\r", i[0], i[1], i[2], i[3]);
return 0;
}
Результат при использовании long double
выглядит примерно так: с неизменными отмеченными цифрами, а все остальные в конечном итоге меняются по мере увеличения и увеличения числа:
5636666b-c03ef3e0-00223fd8-deadbeef
^^ ^^^^^^^^
Это говорит о том, что это не 80-битное число. 80-битное число имеет 18 шестнадцатеричных цифр. Я вижу 22 шестнадцатеричных цифры, которые выглядят намного больше, чем 96-битное число (24 шестнадцатеричных разряда). Он также не является 128-битным числом, так как 0xdeadbeef
не затрагивается, что согласуется с sizeof
, возвращающим 12.
Выход для __int128
выглядит как просто 128-битное число. Все биты в конце концов перевернуты.
Компиляция с -m128bit-long-double
делает не выравнивание long double
до 128 бит с 32-разрядным нулевым заполнением, как указано в документации. Он также не использует __int128
, но, по-видимому, выравнивается до 128 бит, заполняя его значением 0x7ffdd000
(?!).
Кроме того, LDBL_MAX
работает как +inf
для long double
и __float128
. Добавление или вычитание числа, такого как 1.0E100
или 1.0E2000
в/из LDBL_MAX
, приводит к тому же битовому шаблону.
До сих пор я полагал, что константы foo_MAX
должны были содержать наибольшее представимое число, которое не является +inf
(по-видимому, это не так?). Я также не совсем уверен, как 80-битное число могло бы действовать как +inf
для 128-битного значения... возможно, я просто слишком устал в конце дня и сделал что-то не так.