1

I use C to do computation using the following code:

#include <stdio.h>
#include <math.h>

void main() {
    float x = 3.104924e-33;
    int i = 6000, j = 1089;
    float value, value_inv;

    value = sqrt(x / ((float)i * j));
    value_inv = 1. / value;

    printf("value = %e\n", value);
    printf("value_inv = %e\n", value_inv);
}

We can see, in fact, value = 2.18e-20. This does not exceed the boundary of float data type in C. But why the computer gives me

value = 0.000000e+00
value_inv = inf

Does anybody know why it happens and how to solve this problem without changing data type to double?

chqrlie
  • 131,814
  • 10
  • 121
  • 189
David
  • 31
  • 5

5 Answers5

4

OP's float apparently does not support sub-normals. C allows non-support.

Does anybody know why it happens and how to solve this problem without changing data type to double?

This may be a implementation detail or due to a compiler option. Without changing to double, look to a different compiler or options. Look at options concerning sub-normal support, precision used for intermediate calculation and optimization levels (which sometimes short edge change cases like this.)


On my machine which does handle sub-normals, using C11, FLT_TRUE_MIN, smallest non-zero float is smaller than FLT_MIN, the smallest normal non-zero float.

#include<float.h>
float xx = x/((float)i*j);
printf("xx = %e %e %e\n",xx, FLT_MIN, FLT_TRUE_MIN);

Output

xx = 4.751943e-40 1.175494e-38 1.401298e-45

In OP's case, without sub-normal support, xx became 0.0f and led to the undesired output.


Using double math will handle the small intermediate float values.

value = sqrt(x/(1.0*i*j));  // Form product with `double` math
value_inv = 1.0f/value;     // Here we can just use float math
printf("value = %e\n",value);
printf("value_inv = %e\n",value_inv);

Output

value = 2.179897e-20
value_inv = 4.587373e+19
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
1

On my computer (Ryzen 2700X, x86_64) the results are:

value = 2.179897e-020
value_inv = 4.587373e+019

You can try 1.f instead 1. , which actually is a double:

value_inv = 1.f/value;
1

Apparently your system hasn't support more digit for float. On my system the output is:

value = 2.179895e-020
value_inv = 4.587376e+019
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
setareh
  • 39
  • 4
  • this doesn't add any more information than [Arseniy Karpenko's answer](https://stackoverflow.com/a/64299441/995714) above. Did you read other answers before posting this? – phuclv Oct 11 '20 at 05:18
  • This answer does report a different result than @Arseniy Karpenko. and that makes it informative. This answer appears to be on a system using `float` with sub-normals whereas Arseniy Karpenko result appears that the intermediate `float` calculation used `double` math - something allowed by C, even without explicit `double` code. See `FLT_EVAL_METHOD`. – chux - Reinstate Monica Oct 11 '20 at 14:27
0

I got the answer by myself. I should change sqrt(x/((float)i*j)) to sqrt((double)x/((double)i*j)). After this, I can get correct result:

value = 2.179897e-20
value_inv = 4.587373e+19
David
  • 31
  • 5
0

There is no reason to use float instead of double for such computations:

  • 3.104924e-33 is a double constant, it gets converted to float upon assignment, with a potential loss of precision
  • sqrt gets a double argument and returns a double value. Implicit conversions occur again with potential loss of precision.
  • 1. / value computes with the type double because 1. has this type. value gets converted before the division and the result is converted to float to store to value_inv.
  • value and value_inv are implicitly converted to double when passed to printf.

All these conversions may incur loss of precision or even truncation to 0.. You should instead always use double unless there is a strong requirement to use float:

#include <stdio.h>
#include <math.h>

int main() {
    double x = 3.104924e-33;
    int i = 6000, j = 1089;
    double value, value_inv;

    value = sqrt(x / ((double)i * j));
    value_inv = 1. / value;

    printf("value = %e\n", value);
    printf("value_inv = %e\n", value_inv);
    return 0;
}

If for some reason you are required to use float, be careful to avoid unneeded conversions:

#include <stdio.h>
#include <math.h>

int main() {
    float x = 3.104924e-33F;
    int i = 6000, j = 1089;
    float value, value_inv;

    value = sqrtf(x / ((float)i * j));
    value_inv = 1.F / value;

    printf("value = %e\n", value);
    printf("value_inv = %e\n", value_inv);
    return 0;
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • Notice the colorizer bug on the float constant `3.104924e-33F`: the `F` does not have the proper style, albeit `1.F` is correct. – chqrlie Oct 11 '20 at 15:04