Как сделать переменную с плавающей запятой комплексной или двойной комплексной в зависимости от ввода пользователя? Я пытаюсь построить множество Мандельброта, и у меня один и тот же длинный код для float и double, но в некоторых случаях float недостаточно точен, а double работает слишком медленно. Я запускаю его на низкопроизводительном калькуляторе, поэтому время с плавающей запятой составляет менее 1 секунды, а двойное — не менее 5 секунд. Я хотел бы позволить пользователю выбирать точность вместо скорости или скорость вместо точности. Я хотел бы получить что-то вроде этого:
int user_input;
scanf("%d", &user_input);
switch (user_input) {
case 0:
float z;
float c;
break;
case 1:
double z;
double c;
break;
}
for(int i=0;i<1920;i++){
for(int j=0;j<1080;j++){
z=0.0+0.0*I;
c=(i+j*I)/Zoom
}}
Ожидаемый результат: если пользователь вводит 0, то все вычисления выполняются с использованием чисел с плавающей точкой, а если пользователь вводит 1, вычисления выполняются с использованием чисел типа double.
Но это вызывает ошибку: конфликтующие типы для «z»; иметь «двойное», предыдущее объявление «z» с типом «float».
Возможно ли что-то подобное в C?
Я попробовал создать две разные переменные для float и double, но менять их во всем коде (у меня гораздо больше кода) было бы долго и утомительно.
@JohnBollinger Да, я делаю это на калькуляторе, поэтому изменения очень заметны. Менее 1 секунды для плавающего режима и не менее 5 секунд для двойного
Какой-то процессор с аппаратной поддержкой float, но двойной эмулируется программно?
Процессор 32-битный, так что да, эмулируется double, но он медленный.
Из этого не следует, что 32-битный процессор имеет FPU одинарной точности, так что это нелогично. Это верно для FPU Cortex-M4, но не в целом.
Это не единственная ошибка: z и c даже не входят в область применения в момент использования.
Вы не можете. Компилятору необходимо знать тип переменной, чтобы сгенерировать соответствующий код.
В вашем сценарии я бы создал программу, которую можно было бы скомпилировать в два разных исполняемых файла: один, использующий float
, и другой, использующий double
.
#ifdef USEDOUBLE
typedef double Number;
#else
typedef float Number;
#endif
Number z;
gcc plot.c -o plot_float
gcc -DUSEDOUBLE plot.c -o plot_double
Другая возможность — создать две версии всего. На самом деле вы не хотите писать всего по две штуки, поэтому вы должны использовать предыдущий подход для создания двух библиотек, а затем загружать подходящую во время выполнения.
Он спросил о решении во время выполнения
Ему придется это делать во многих местах, поскольку функции с плавающей запятой (например, из math.h) — это не то же самое, что double double. В противном случае числа с плавающей запятой будут преобразованы в двойные и обратно в числа с плавающей запятой.
Это верно. Если скорость является проблемой, вы должны скомпилировать либо для double
, либо для float
. Вы не можете волшебным образом сделать и то, и другое.
@0___________, Re «Он спросил о решении во время выполнения», вы не читали ответ? Я сказал, что это невозможно. Я также предоставил два обходных пути, второй из которых — «решение во время выполнения».
Вы не можете изменить тип переменной после ее компиляции, но вы можете использовать функциональный макрос, чтобы создать другую версию одной и той же функции для каждого типа, который вы можете использовать, без необходимости дублировать код, как вы могли бы использовать шаблоны на других языках:
#include <stdio.h>
#define MANDELBROT(T) \
void mandelbrot_##T(T I, T Zoom) { \
T z, c; \
for(int i=0;i<1920;i++){ \
for(int j=0;j<1080;j++){ \
z=0.0+0.0*I; \
c=(i+j*I)/Zoom; \
} \
} \
}
MANDELBROT(double)
MANDELBROT(float)
int main(void) {
int user_input;
scanf("%d", &user_input);
double Zoom = 1.0, I = 1.0;
if (user_input) {
mandelbrot_double(I, Zoom);
}
else {
mandelbrot_float(I, Zoom);
}
}
Если у вас есть куча подобных функций, и вы не хотите объявлять абсолютно все как макрос (поскольку размещение обратной косой черты после каждой строки раздражает, и отладчикам сложнее определить, в каких строках что-то определено ), вместо этого вы можете сделать это следующим образом:
шаблоны.ч:
/*
By wrapping all function names in the NAME macro, we get a different identifier
depending on whether we defined T as double or float before #including this file:
NAME("foo") will expand to "foo_double" if T is double.
Sometimes we have to do some minor wizardry to get a macro to fully expand,
which is why we have NAME2 and NAME3. Passing a macro to another
macro sets a dirty-bit that causes it to be expanded properly.
*/
#define NAME3(S,U) S ## _ ## U
#define NAME2(S,U) NAME3(S,U)
#define NAME(S) NAME2(S,T)
void NAME(mandelbrot)(T I, T Zoom) {
T z, c;
for(int i=0;i<1920;i++){
for(int j=0;j<1080;j++){
z=0.0+0.0*I;
c=(i+j*I)/Zoom;
}
}
}
#undef T
#undef NAME
#undef NAME2
#undef NAME3
основной.с:
#define T double
#include "templates.h"
#define T float
#include "templates.h"
int main(void) {
mandelbrot_double(1,1);
mandelbrot_float(1,1);
}
Примечание: как отметил в комментариях Чукс, I
— это макрос из complex.h, поэтому вам следует назвать его как-нибудь по-другому (если только этот макрос не является тем, что вы собирались использовать).
Лучше всего сочетать с общей математикой типа C99, чтобы sqrt()
и т. д. в версии с плавающей запятой не перетягивались в двойные значения.
@Шон, время компиляции тоже завершено. Обобщенные шаблоны C не разрешаются во время выполнения
@0___________ Чтобы уточнить, обычный sqrt()
принимает двойное значение и возвращает двойное, что требует преобразований между числами с плавающей запятой и двойными числами. Использование подпрограмм tgmath вместо этого приведет к использованию sqrtf()
в версии с плавающей запятой генерируемой функции, оставаясь на быстром пути. Да, это происходит во время компиляции. Что-то в моем первом комментарии предполагает, что это не так?
@chux-ReinstateMonica Этот идентификатор взят из кода OP. Я оставил код без изменений, за исключением тех случаев, когда это необходимо для работы макроса, чтобы не смешивать два разных набора изменений (кроме того, я не заметил конфликта имен, когда копировал его). Но вы правы насчет конфликта имен; Я добавлю примечание на этот счет.
@chux-ReinstateMonica OP упоминает сложные типы double и float. Спорим, они просто забыли включить сложную часть в этот нерабочий фрагмент кода.
Извините, когда я читал z=0.0+0.0*I;
, я думал, что ОП умножается на индекс i
. Предложение удалено.
Единственное жизнеспособное решение в современных системах, поддерживающих динамическое связывание, — это скомпилировать одну версию вашей программы с помощью float
, а другую — с помощью double
. Затем основная программа загрузит правильную версию динамической библиотеки и выполнит запрошенную версию.
В моем ответе упоминается об этом («создайте две библиотеки, а затем загрузите соответствующую во время выполнения».) Ответ, который вы только что сказали, не ответил на вопрос ОП. Так что же это? Это ответ на вопрос или нет?
Это именно тот ответ, который я хотел!
Вы уверены, что
double
представляет для вас проблему с производительностью по сравнению сfloat
? Возможно, я мог бы увидеть это, если бы вы перемещали большое их количество туда и обратно между памятью и процессором, что могло бы быть проблемой с пропускной способностью памяти, но в вычислительном отношенииdouble
обычно не медленнее, чемfloat
. На самом деле,double
нередко бывает быстрее.