Какой смысл float_t и когда его следует использовать? - PullRequest
17 голосов
/ 22 марта 2011

Я работаю с клиентом, который использует старую версию GCC (если быть точным 3.2.3), но хочет обновить ее, и одна из причин, которая была названа камнем преткновения при обновлении до более новой версии, - это различия в размеретипа float_t, что, безусловно, является правильным:

В GCC 3.2.3

sizeof(float_t) = 12
sizeof(float) = 4
sizeof(double_t) = 12
sizeof(double) = 8

В GCC 4.1.2

sizeof(float_t) = 4
sizeof(float) = 4
sizeof(double_t) = 8
sizeof(double) = 8

но в чем причина этой разницы?Почему размер стал меньше и когда следует и не следует использовать float_t или double_t?

Ответы [ 3 ]

12 голосов
/ 22 марта 2011

Причина для float_t заключается в том, что для некоторых процессоров и компиляторов используется больший тип, например long double для float может быть более эффективным, поэтому float_t позволяет компилятору использовать больший тип вместо float.

Таким образом, в случае OP, использующих float_t, изменение размера - это то, что допускает стандарт. Если исходный код хотел использовать меньшие размеры float, он должен использовать float.

В open-std doc * есть некоторое обоснование

например определения типа float_t и double_t (определены в ), предназначены для эффективное использование архитектур с более эффективные, более широкие форматы. Приложения

9 голосов
/ 21 апреля 2014

«Почему» в том, что некоторые компиляторы возвращают значения с плавающей запятой в регистре с плавающей запятой. Эти регистры имеют только один размер. Например, в X86 его ширина составляет 80 бит. Результаты функции, которая возвращает значение с плавающей запятой, будут помещены в этот регистр независимо от того, был ли тип объявлен как float, double, float_t или double_t. Если размер возвращаемого значения и размер регистра с плавающей запятой различаются, то в какой-то момент потребуется инструкция для округления до желаемого размера.

Такой же тип преобразования необходим и для целых чисел, но для последующих сложений и вычитаний нет накладных расходов, потому что есть инструкции, чтобы выбрать, какие байты включить в операцию. Правила для преобразования целых чисел в меньший размер указывают, что отбрасываются старшие значащие биты, поэтому результат уменьшения может привести к радикально другому результату (например, (короткий) (2147450880) -> -32768), но для какая-то причина, которая, кажется, в порядке с сообществом программистов.

При выполнении уменьшения чисел с плавающей точкой результат указывается округленным до ближайшего представимого числа. Если бы целые числа подчинялись одним и тем же правилам, то приведенный выше пример усек бы таким образом (short) (2147450880) -> +32767. Очевидно, что для выполнения такой операции требуется немного больше логики, чем простое усечение старших битов. С плавающей точкой, экспонента и значение и изменяют размеры между плавающим, двойным и длинным двойным, так что это сложнее. Кроме того, существуют проблемы преобразования между бесконечностью, NaN, нормализованными числами и перенормированными числами, которые необходимо учитывать. Аппаратное обеспечение может реализовать эти преобразования за то же время, что и целочисленное сложение, но если преобразование необходимо реализовать в программном обеспечении, может потребоваться 20 инструкций, что может оказать заметное влияние на производительность. Поскольку модель программирования на языке C гарантирует получение одинаковых результатов независимо от того, реализована ли плавающая точка в аппаратном или программном обеспечении, программное обеспечение обязано выполнять эти дополнительные инструкции, чтобы соответствовать вычислительной модели. Типы float_t и double_t были разработаны для предоставления наиболее эффективного типа возвращаемого значения .

Компилятор определяет FLT_EVAL_METHOD , который указывает, какая точность должна использоваться в промежуточных вычислениях. С целыми числами правило состоит в том, чтобы делать промежуточные вычисления, используя самую высокую точность вовлеченных операндов. Это будет соответствовать FLT_EVAL_METHOD == 0. Тем не менее, исходный K & R указывал, что все промежуточные вычисления должны выполняться дважды, что приводит к FLT_EVAL_METHOD == 1. Однако, с введением стандарта IEEE с плавающей запятой, на некоторых платформах, особенно Macintosh PowerPC и Windows X86, стало обычным делом выполнять промежуточные вычисления в длинных двойных - 80 битах, что приводит к FLT_EVAL_METHOD == 2.

На регрессионное тестирование будет влиять вычислительная модель FLT_EVAL_METHOD . Таким образом, ваш код регрессии должен учитывать это. Один из способов - проверить FLT_EVAL_METHOD и иметь разные ветви для каждой модели. Аналогичный метод будет проверять sizeof (float_t) и иметь разные ветви. Третий метод - использовать какой-нибудь эпсилон, который будет использоваться для проверки того, достаточно ли близки результаты.

К сожалению, есть некоторые вычисления, которые принимают решение на основе результатов вычислений, что приводит к true или false , который не может быть решен с помощью эпсилона. Это происходит в компьютерной графике, например, чтобы решить, находится ли точка внутри или снаружи многоугольника, который определяет, должен ли быть заполнен определенный пиксель. Если ваша регрессия включает один из них, вы не можете использовать метод epsilon и должны использовать разные ветви в зависимости от вычислительной модели.

Еще один способ разрешить регрессию решений между моделями - явное приведение результата к определенной желаемой точности. Это работает в большинстве случаев на многих компиляторах, но некоторые компиляторы считают, что они умнее вас, и отказываются выполнять преобразование. Это происходит в случае, когда промежуточный результат сохраняется в регистре, но используется в последующих вычислениях. В промежуточном результате вы можете отбрасывать столько точности, сколько хотите, но компилятор ничего не сделает - , если вы не объявите промежуточный результат как volatile . Это затем вынуждает компилятор уменьшать размер и сохранять промежуточный результат в переменной указанного размера в памяти , а затем извлекать его при необходимости для вычислений. Стандарт IEEE с плавающей запятой точный для элементарных операций (+ - * /) и квадратного корня. Я полагаю, что sin (), cos (), exp (), log () и т. Д. Указаны в пределах 2 ULP (единицы в наименьшей значимой позиции) от ближайшего числово представимого результата. Длинный двойной (80 бит) формат был разработан, чтобы позволить вычисление этих других трансцендентных функций точно до ближайшего числово представимого результата.

Это охватывает многие проблемы, поднятые (и подразумеваемые) в этой теме, но не отвечает на вопрос, когда вам следует использовать типы float_t и double_t. Очевидно, это необходимо сделать при взаимодействии с API, который использует эти типы, особенно при передаче адреса одного из этих типов.

Если вас больше всего беспокоит производительность, то вы можете рассмотреть возможность использования типов float_t и double_t в своих вычислениях и API. Но наиболее вероятно, что увеличение производительности, которое вы получите, не будет ни измеримым, ни заметным.

Однако, если вас беспокоит регрессия между разными компиляторами и разными машинами, вам, вероятно, следует избегать этих типов в максимально возможной степени и свободно использовать приведение типов для обеспечения кросс-платформенной совместимости.

6 голосов
/ 22 марта 2011

Стандарт C99 гласит:

Типы float_t double_t

являются плавающими типами, по крайней мере, такими же широкими, как float и double, соответственно, и такими, что double_t по крайней мере такой же ширины, как float_t. Если FLT_EVAL_METHOD равно 0, float_t и double_t равны float и double соответственно; если FLT_EVAL_METHOD равно 1, они оба double; если FLT_EVAL_METHOD равно 2, они оба long double; а для других значений FLT_EVAL_METHOD они иначе определяются реализацией.178)

И действительно, в предыдущих версиях gcc они были определены как long double по умолчанию.

...