Как изменить точность плавающей точки в C во время выполнения?

Как сделать переменную с плавающей запятой комплексной или двойной комплексной в зависимости от ввода пользователя? Я пытаюсь построить множество Мандельброта, и у меня один и тот же длинный код для 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, но менять их во всем коде (у меня гораздо больше кода) было бы долго и утомительно.

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

John Bollinger 01.08.2024 19:55

@JohnBollinger Да, я делаю это на калькуляторе, поэтому изменения очень заметны. Менее 1 секунды для плавающего режима и не менее 5 секунд для двойного

Lol ilol 01.08.2024 20:05

Какой-то процессор с аппаратной поддержкой float, но двойной эмулируется программно?

Shawn 01.08.2024 20:11

Процессор 32-битный, так что да, эмулируется double, но он медленный.

Lol ilol 01.08.2024 20:15

Из этого не следует, что 32-битный процессор имеет FPU одинарной точности, так что это нелогично. Это верно для FPU Cortex-M4, но не в целом.

Clifford 01.08.2024 20:42

Это не единственная ошибка: z и c даже не входят в область применения в момент использования.

Clifford 01.08.2024 20:45
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
6
69
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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

В вашем сценарии я бы создал программу, которую можно было бы скомпилировать в два разных исполняемых файла: один, использующий 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

Другая возможность — создать две версии всего. На самом деле вы не хотите писать всего по две штуки, поэтому вы должны использовать предыдущий подход для создания двух библиотек, а затем загружать подходящую во время выполнения.

Он спросил о решении во время выполнения

0___________ 01.08.2024 19:58

Ему придется это делать во многих местах, поскольку функции с плавающей запятой (например, из math.h) — это не то же самое, что double double. В противном случае числа с плавающей запятой будут преобразованы в двойные и обратно в числа с плавающей запятой.

0___________ 01.08.2024 20:00

Это верно. Если скорость является проблемой, вы должны скомпилировать либо для double, либо для float. Вы не можете волшебным образом сделать и то, и другое.

Dúthomhas 01.08.2024 20:06

@0___________, Re «Он спросил о решении во время выполнения», вы не читали ответ? Я сказал, что это невозможно. Я также предоставил два обходных пути, второй из которых — «решение во время выполнения».

ikegami 01.08.2024 20:23

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

#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() и т. д. в версии с плавающей запятой не перетягивались в двойные значения.

Shawn 01.08.2024 20:13

@Шон, время компиляции тоже завершено. Обобщенные шаблоны C не разрешаются во время выполнения

0___________ 01.08.2024 20:27

@0___________ Чтобы уточнить, обычный sqrt() принимает двойное значение и возвращает двойное, что требует преобразований между числами с плавающей запятой и двойными числами. Использование подпрограмм tgmath вместо этого приведет к использованию sqrtf() в версии с плавающей запятой генерируемой функции, оставаясь на быстром пути. Да, это происходит во время компиляции. Что-то в моем первом комментарии предполагает, что это не так?

Shawn 01.08.2024 20:33

@chux-ReinstateMonica Этот идентификатор взят из кода OP. Я оставил код без изменений, за исключением тех случаев, когда это необходимо для работы макроса, чтобы не смешивать два разных набора изменений (кроме того, я не заметил конфликта имен, когда копировал его). Но вы правы насчет конфликта имен; Я добавлю примечание на этот счет.

Ray 01.08.2024 20:50

@chux-ReinstateMonica OP упоминает сложные типы double и float. Спорим, они просто забыли включить сложную часть в этот нерабочий фрагмент кода.

Shawn 01.08.2024 20:54

Извините, когда я читал z=0.0+0.0*I;, я думал, что ОП умножается на индекс i. Предложение удалено.

chux - Reinstate Monica 01.08.2024 20:54
Ответ принят как подходящий

Единственное жизнеспособное решение в современных системах, поддерживающих динамическое связывание, — это скомпилировать одну версию вашей программы с помощью float, а другую — с помощью double. Затем основная программа загрузит правильную версию динамической библиотеки и выполнит запрошенную версию.

В моем ответе упоминается об этом («создайте две библиотеки, а затем загрузите соответствующую во время выполнения».) Ответ, который вы только что сказали, не ответил на вопрос ОП. Так что же это? Это ответ на вопрос или нет?

ikegami 01.08.2024 20:31

Это именно тот ответ, который я хотел!

Lol ilol 01.08.2024 22:00

Другие вопросы по теме