Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to change the precision of a floating point in C at runtime?

Tags:

c

types

precision

How to make a variable float complex or double complex depending on user input? I'm trying to plot the Mandelbrot set and I have the same long code for float and double but in some cases float is not precise enough and double is too slow. I'm running it on a low-performance calculator, so float is less than 1 second and double takes at least 5 seconds. I'd like to let the user choose precision over speed or speed over precision. I would like to get something like this:

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
}}

Expected outcome: if the user types 0, then all the calculations are performed using floats, and if the user types 1, the calculations are performed using doubles.

But this throws error: conflicting types for ‘z’; have ‘double’, previous declaration of ‘z’ with type ‘float’.

Is something like this possible in C?

I tried making two different varables for float and double but changing them in all the code (I have way more code) would be long and tedious.

like image 380
shirAko Avatar asked Sep 01 '25 23:09

shirAko


2 Answers

You can't change the type of a variable after it's been compiled, but you can use a function-like macro to make a different version of the same function for each type you might use without needing to duplicate code, much as you might use templates in other languages:

#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);
    }
}

If you've got a bunch of functions like this, and you don't want to declare absolutely everything as a macro (since putting backslashes after every line is annoying, and it makes it harder for debuggers to tell what lines things are defined on), you can instead do it like this:

templates.h:

/* 
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

main.c:

#define T double
#include "templates.h"
#define T float
#include "templates.h"

int main(void) {
    mandelbrot_double(1,1);
    mandelbrot_float(1,1);
}

A side note: as chux pointed out in the comments, I is a macro from complex.h, so you should name that something else (unless that macro is what you were intending to use).

like image 55
Ray Avatar answered Sep 03 '25 13:09

Ray


You can't. The compiler needs to know the type of the variable in order to generate the appropriate code.

In your scenario, I would create a program that could be compiled into two different executables: one that uses float, and one that uses 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

Another possibility is to create two versions of everything. You don't want to actually write two of everything, so you'd use the earlier approach to create two libraries, then load the appropriate one at run-time.

like image 33
ikegami Avatar answered Sep 03 '25 12:09

ikegami