C 'дженерики' удваиваются и плавают

У меня есть функция в C, которая принимает и возвращает двойное число (и использует несколько двойных значений внутри). Есть ли хороший способ сделать вторую версию функции, точно такую ​​же, только с float вместо double? Также следует обновить такие константы, как DBL_EPSILON.

Я полагаю, что мог бы сделать это с помощью препроцессора, но это кажется неудобным (и, вероятно, сложным для отладки, если есть ошибка компиляции). Что рекомендуют лучшие практики? Я не могу представить, что я единственный, кому пришлось иметь дело с этим.

Изменить: я забыл, это stackoverflow, поэтому я не могу просто задать вопрос, я должен оправдаться. У меня есть код, который в этом случае очень чувствителен к точности; стоимость использования двойников, а не поплавков, составляет от 200% до 300%. До сих пор мне требовалась только двойная версия — когда она мне была нужна, я хотел как можно большей точности, независимо от необходимого времени (в этом приложении это был крошечный процент). Но теперь я нашел применение, которое чувствительно к скорости и не выигрывает от дополнительной точности. Я содрогнулся от своей первой мысли, которая заключалась в том, чтобы скопировать всю функцию и заменить типы. Затем я подумал, что экспертам в SO будет известен лучший подход, поэтому я разместил его здесь.


person Charles    schedule 11.08.2011    source источник
comment
Вы уверены, что хотите этого? Многие современные аппаратные средства оптимизированы для работы с числами типа double, а не с числами с плавающей запятой. Вы можете обнаружить, что передача аргумента с плавающей запятой (который неявно преобразуется в число с плавающей запятой) и последующее получение результата типа double (который затем неявно преобразуется в число с плавающей запятой) выполняется по меньшей мере так же быстро, как и выполнение всех вычислений с числом с плавающей запятой. (Я предполагаю, что производительность является вашей мотивацией здесь.) Попробуйте сделать быстрое и грязное копирование и вставку, заменив double на float, и измерьте производительность.   -  person Keith Thompson    schedule 11.08.2011
comment
@keith правда о производительности float и double. тем не менее, в последний раз, когда я имел дело с числами с плавающей запятой и двойными, причина, по которой я выбрал числа с плавающей запятой, заключалась в том, что приложение выполняло интенсивный DSP для сигналов, которые при сохранении в виде двойных значений намного быстрее переполняли бы конвейеры, поэтому в этом случае мы жертвовали некоторыми точность, чтобы ЦП тратил как можно меньше времени на ожидание данных.   -  person shelleybutterfly    schedule 11.08.2011
comment
@Keith Thompson: Производительность примерно втрое (от 1/4 до 1/3 времени) с поплавками.   -  person Charles    schedule 11.08.2011


Ответы (4)


не знаю о «лучших практиках», но препроцессор определенно был первым, что пришло мне в голову. это похоже на шаблоны в C++.

[править: и в ответе Иисуса Рамоса упоминаются разные буквы функций с разными типами в библиотеках, и вы, вероятно, захотите это сделать]

вы создаете отдельный исходный файл со своими функциями, везде у вас есть двойное изменение его на FLOATING_POINT_TYPE (просто для примера), а затем дважды включаете свой исходный файл из другого файла. (или любой другой метод, который вы выберете, вам просто нужно иметь возможность в конечном итоге обрабатывать файл дважды, один раз с каждым типом данных, как вы определяете.) [также для определения символа, добавленного для различения разных версий функции, определите FLOATING_POINT_TYPE_CHAR]

#define FLOATING_POINT_TYPE double
#define FLOATING_POINT_TYPE_CHAR d
#include "my_fp_file.c"
#undef FLOATING_POINT_TYPE_CHAR
#undef FLOATING_POINT_TYPE

#define FLOATING_POINT_TYPE float
#define FLOATING_POINT_TYPE_CHAR f
#include "my_fp_file.c"
#undef FLOATING_POINT_TYPE
#undef FLOATING_POINT_TYPE_CHAR

тогда вы также можете использовать аналогичную стратегию для своих прототипов в своих заголовках.

но, поэтому в вашем заголовочном файле вам понадобится что-то вроде:

#define MY_FP_FUNC(funcname, typechar) \
   funcname##typechar

и для ваших определений/прототипов функций:

FLOATING_POINT_TYPE
  MY_FP_FUNC(DoTheMath, FLOATING_POINT_TYPE_CHAR) 
  (
    FLOATING_POINT_TYPE Value1,
    FLOATING_POINT_TYPE Value2
  );

и так далее.

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

Кстати, для примера такой стратегии в зрелом программном обеспечении вы можете проверить FFTW (fftw.org), хотя он немного сложнее, чем пример, я думаю, что он использует в основном ту же стратегию.

person shelleybutterfly    schedule 11.08.2011

Не беспокойтесь.

За исключением нескольких конкретных аппаратных реализаций, нет никаких преимуществ в наличии float версии double функции. Большая часть оборудования IEEE 754 выполняет все вычисления в 64- или 80-битной арифметике внутри и усекает результаты до желаемой точности при сохранении.

Совершенно нормально возвращать double для использования или хранения как float. Создание float версии той же логики вряд ли будет работать быстрее или больше подходит для чего-либо. Единственным исключением, которое приходит на ум, будут алгоритмы, оптимизированные для GPU, которые не поддерживают 64+ битные операции.

person wallyk    schedule 11.08.2011
comment
Не очень новые карты nvidia работают лучше с floats - person Tom; 11.08.2011
comment
На самом деле на более новом оборудовании, когда происходит операция с плавающей запятой, она в любом случае расширяется до двойной точности, по крайней мере, для более новых 64-битных архитектур, но для этого требуется расширение, также можно использовать удвоения. - person Jesus Ramos; 11.08.2011
comment
@Tom, специфичный для графических процессоров, который в зависимости от того, что вы делаете, может фактически выполнять операции быстрее с меньшими битовыми полями, в этом случае плавает. - person Jesus Ramos; 11.08.2011
comment
Написанная мной функция работает с числами с плавающей запятой и числами с двойной точностью по-разному, поскольку это зависит от количества битов точности. Я бы не спросил иначе. :) - person Charles; 11.08.2011
comment
@Charles: какое целевое оборудование? - person wallyk; 11.08.2011
comment
@wallyk: Феном II. Однако этот конкретный фрагмент кода не сильно зависит от аппаратного обеспечения. - person Charles; 11.08.2011

Как видно из большинства стандартных библиотек, такие методы на самом деле не переопределяются, просто создаются новые методы. Например:

void my_function(double d1, double d2);
void my_functionf(float f1, float f2);

Многие из них имеют разные последние буквы в методе, чтобы указать, что это похоже на переопределение метода для разных типов. Это также относится к возвращаемым типам, таким как функция atoi, atol, atof.... и т. д.

В качестве альтернативы оберните свою функцию в макрос, который добавляет тип в качестве аргумента, например

#define myfunction(arg1, arg2, type) .... 

Таким образом, это намного проще, так как теперь вы можете просто обернуть все своим типом, избегая копирования и вставки функции, и вы всегда можете проверить тип.

person Jesus Ramos    schedule 11.08.2011
comment
Это разумное соглашение об именах, но мне было интересно, есть ли СУХОЙ способ реализовать это. - person Charles; 11.08.2011
comment
@ Иисус +1 за указание на это, Чарльз, этот пост напомнил мне о необходимости сделать это; я изменил свой ответ, чтобы включить эту стратегию. - person shelleybutterfly; 11.08.2011
comment
Нет разумного способа сделать это, поскольку C не поддерживает перегрузку методов. Это один из самых простых способов, который не перегружен кучей директив препроцессора. Если вы не хотите превратить свою функцию в макрос.... - person Jesus Ramos; 11.08.2011

В этом случае я бы сказал, что наилучшей практикой было бы написание специального инструмента codegen, который будет брать «общий» код и создавать новую версию double и float каждый раз перед компиляцией.

person Petr Abdulin    schedule 11.08.2011
comment
+1 b/c в зависимости от специфики, это может быть проще и чище, чем куча беспорядка препроцессора. :) (также может дать гораздо более чистую отладку...) - person shelleybutterfly; 11.08.2011