длинный двойной (специфический GCC) и float128
я ищу подробную информацию о long double
и __float128
в GCC / x86 (больше из любопытства, чем из-за реальной проблемы).
немногие люди, вероятно, когда-нибудь понадобится (я только, в первый раз в жизни,истинно нужен double
), но я думаю, что все еще стоит (и интересно) знать, что у вас есть в вашем наборе инструментов и о чем это.
в этом свете, пожалуйста, извините меня несколько открытым вопросы:
- может ли кто-нибудь объяснить обоснование реализации и предполагаемое использование этих типов, также в сравнении друг с другом? Например, являются ли они "реализациями смущения", потому что стандарт допускает тип, и кто-то может пожаловаться, если они только с той же точностью, что и
double
, или они предназначены в качестве первого класса? - кроме того, у кого-то есть хорошая, полезная веб-ссылка для обмена? Поиск в Google на
"long double" site:gcc.gnu.org/onlinedocs
не дал мне много полезного. - предполагая, что общая мантра "если вы считаете, что вам нужен double, вы, вероятно, не понимаете плавающую точку" не применяется, т. е. вы действительно нужно больше точности, чем просто
float
, и вам все равно, будут ли сожжены 8 или 16 байтов памяти... разумно ли ожидать, что можно просто перейти кlong double
или__float128
вместоdouble
без значительного представления удар? - функция "расширенной точности" процессоров Intel исторически была источником неприятных сюрпризов, когда значения перемещались между памятью и регистрами. Если на самом деле хранятся 96 бит, то
long double
type должен устранить эту проблему. С другой стороны, я понимаю, чтоlong double
тип является взаимоисключающим с-mfpmath=sse
, поскольку в SSE нет такой вещи, как "расширенная точность".__float128
, С другой стороны, должен отлично работать с SSE math (хотя и в отсутствии из четырех точных инструкций, конечно, не на базе инструкций 1:1). Прав ли я в своих предположениях?
(3. и 4. возможно, это можно выяснить с помощью некоторых работ, потраченных на профилирование и разборку, но, может быть, кто-то другой имел ту же мысль ранее и уже сделал эту работу.)
фон (это часть TL; DR):
Я сначала споткнулся long double
потому что я смотрел DBL_MAX
на <float.h>
, и incidentially LDBL_MAX
на следующей строке. "О, смотрите, GCC на самом деле имеет 128-битные двойники, не то, чтобы они мне нужны, но... круто" была моя первая мысль. Сюрприз, сюрприз: sizeof(long double)
возвращает 12... подожди, ты имеешь в виду 16?
стандарты C и c++ неудивительно, что не дают очень конкретного определения типа. C99 (6.2.5 10) говорит, что числа double
- это подмножество long double
тогда как C++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-%08xr", 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
to / from LDBL_MAX
результаты в том же битовом шаблоне.
До сих пор я считал, что foo_MAX
константы должны были содержать наибольшее представимое число, которое не +inf
(по-видимому, это не так?). Я также не совсем уверен, как в 80-разрядное число может действовать как +inf
для 128-битного значения... может быть, я просто слишком устал в конце дня и сделал что-то не так.
4 ответов
Объявление 1.
эти типы предназначены для работы с числами с огромным динамическим диапазоном. Длинный двойной реализован родным способом в x87 FPU. Я подозреваю, что 128b double будет реализован в программном режиме на современных x86s, поскольку нет аппаратного обеспечения для вычислений в аппаратном обеспечении.
самое смешное, что он часто приходится делать много операций с плавающей точкой в строку, и промежуточные результаты не сохраняются в переменных а хранится в регистрах FPU, используя полную точность. Вот почему сравнение:
double x = sin(0); if (x == sin(0)) printf("Equal!");
не безопасно и не может быть гарантировано работать (без дополнительных переключателей).
объявление. 3.
существует влияние на скорость в зависимости от того, какую точность вы используете. Вы можете изменить используемую точность FPU, используя:
void
set_fpu (unsigned int mode)
{
asm ("fldcw %0" : : "m" (*&mode));
}
Это будет быстрее для более коротких переменных, медленнее, дольше. 128bit удваивается, вероятно, будет сделано в программном обеспечении, так что будет много замедлившийся.
это не только о рам памяти впустую, это о кэш впустую. Переход на 80 бит double от 64b double приведет к потере от 33% (32b) до почти 50% (64b) памяти (включая кэш).
объявление 4.
С другой стороны, я понимаю, что двойной взаимно эксклюзив с-mfpmath=sse, так как нет такой вещи, как "extended точность" в SSE. __float128, с другой стороны, должен работать только прекрасно SSE math (хотя и при отсутствии четырехъядерной точности инструкции, конечно, не на базе инструкций 1:1). Я прямо под эти предположения?
блоки FPU и SSE совершенно отдельно. Вы можете писать код с помощью FPU одновременно с SSE. Вопрос в том, что будет генерировать компилятор, если вы ограничите его использование только SSE? Будет ли он пытаться использовать FPU в любом случае? Я занимаюсь некоторым программированием с SSE, и GCC будет генерировать только один SISD самостоятельно. Вы должны помочь ему используйте версии SIMD. __float128, вероятно, будет работать на каждой машине, даже 8-битный AVR uC. В конце концов, это просто игра с битами.
80 бит в шестнадцатеричном представлении на самом деле 20 шестнадцатеричных цифр. Может быть, биты, которые не используются, из какой-то старой операции? На моей машине, я скомпилировал ваш код и только 20 бит изменения в длинных режим: 66b4e0d2-ec09c1d5-00007ffe-deadbeef
в 128-битной версии все биты меняются. Глядя на objdump
похоже, что он использовал эмуляция программного обеспечения, почти нет инструкций FPU.
далее, LDBL_MAX, кажется, работает как +inf для обоих длинных двойных и __float128. Добавление или вычитание числа типа 1.0E100 или 1.0E2000 в / из LDBL_MAX приводит к тому же битовому шаблону. До сих пор это был мой убеждение в том, что константы foo_MAX должны содержать наибольшие представимое число, которое не является +inf (по-видимому, это не кейс?).
Это, кажется, странный...
Я также не совсем уверен, как 80-битное число может предположительно действуйте как +inf для 128-битного значения... может, я просто слишком устал в конце. и сделали что-то не так.
он, вероятно, расширяется. Шаблон, который распознается как +inf в 80-битном, также переводится в +inf в 128-битном float.
IEEE-754 определил 32 и 64 представления с плавающей запятой с целью эффективного хранения данных и 80-битное представление с целью эффективного вычисления. Намерение было то, что дано float f1,f2; double d1,d2;
заявления d1=f1+f2+d2;
будет выполняться путем преобразования аргументов в 80-разрядные значения с плавающей запятой, добавления их и преобразования результата обратно в 64-разрядный тип с плавающей запятой. Это дало бы три преимущества по сравнению с выполнением операций с другими плавающими точками типы напрямую:
хотя для преобразования в/из 32-разрядных типов и 64-разрядных типов потребуется отдельный код или схема, потребуется только одна реализация "добавить", одна реализация "умножить", одна реализация "квадратный корень" и т. д.
хотя в редких случаях использование 80-битного вычислительного типа может дать результаты, которые были бы немного менее точными, чем использование других типов напрямую (в худшем случае ошибка округления 513 / 1024ulp в случаях, когда вычисления на других типах дали бы ошибку 511 / 1024ulp), цепные вычисления с использованием 80-битных типов часто были бы более точными-иногда много точнее--чем вычисления с использованием других типов.
в системе без FPU, разделяющей
double
в отдельный показатель и мантиссу перед выполнением вычислений, нормализацией мантиссы и преобразованием отдельной мантиссы и показателя вdouble
, несколько трудоемкий. Если результат одного вычисления будет использоваться в качестве входных данных для другого и отброшен, использование распакованного 80-битного типа позволит опустить эти шаги.
для того, чтобы этот подход к математике с плавающей запятой был полезен, однако, необходимо, чтобы код мог хранить промежуточные результаты с той же точностью, что и при вычислениях, например,temp = d1+d2; d4=temp+d3;
даст тот же результат, что и d4=d1+d2+d3;
. Из того, что я могу сказать, цель long double
С быть этого типа. К сожалению, хотя K&R спроектировал C так, что все значения с плавающей запятой будут передаваться вариадическим методам одинаково, ANSI C сломал это. В C, как первоначально планировалось, учитывая код float v1,v2; ... printf("%12.6f", v1+v2);
на printf
метод не должен беспокоиться о том,v1+v2
даст float
или double
, так как результат будет принуждаться к известному типу независимо. Далее, Даже если тип v1
или v2
изменено на double
на printf
заявление не придется менять.
ANSI C, однако, требует того кода, который вызывает printf
должен знать, какие аргументы double
и long double
; много кода-если не большинство-кода, который использует long double
но был написан на платформах, где он является синонимом double
не удается использовать правильные спецификаторы формата для long double
значения. Вместо long double
быть 80-битный тип, за исключением, когда прошло как метод с переменным числом аргументов аргумент, в этом случае он будет приведен к 64 битам, многие компиляторы решили сделать long double
синоним double
и не предлагают никаких средств хранения результатов промежуточных вычислений. Поскольку использование расширенного типа точности для вычислений хорошо только в том случае, если этот тип доступен программисту, многие люди пришли к выводу, что расширенная точность считается злом, хотя только неспособность ANSI C разумно обрабатывать вариативные аргументы сделала это проблематичный.
PS -- целевое назначение long double
было бы полезно, если бы также был long float
который был определен как тип, к которому float
аргументы можно было бы наиболее эффективно продвигать; на многих машинах без единиц с плавающей запятой, которые, вероятно, были бы 48-битным типом, но оптимальный размер может варьироваться от 32 бит (на машинах с FPU, который делает 32-битную математику напрямую) до 80 (на машинах, которые используют дизайн, предусмотренный IEEE-754). Слишком поздно., хотя.
Это сводится к разнице между 4.99999999999999999999999 и 5.0.
- хотя диапазон является основным отличием, важна точность.
- эти данные будут необходимы в вычислениях большого круга или координатной математике, которая, вероятно, будет использоваться с системами GPS.
- поскольку точность намного лучше, чем обычная двойная, это означает, что вы можете сохранить обычно 18 значащих цифр без потери точности в проведенные расчеты.
- Extended precision я считаю, что использует 80 бит (используется в основном в процессорах математики), поэтому 128 бит будет намного точнее.
C99 и c++11 добавлены типы float_t
и double_t
которых являются псевдонимами для встроенных типов с плавающей запятой. Грубо говоря,float_t
- тип результата выполнения арифметики среди значений типа float
и double_t
- тип результата выполнения арифметики среди значений типа double
.