简体   繁体   中英

double/float conversion in C

I have this code

#define Third (1.0/3.0)
#define ThirdFloat (1.0f/3.0f)
int main()
{
    double a=1/3;
    double b=1.0/3.0;
    double c=1.0f/3.0f;
    printf("a = %20.15lf, b = %20.15lf, c = %20.15lf\n", a,b,c);
    float d=1/3;
    float e=1.0/3.0;
    float f=1.0f/3.0f;
    printf("d = %20.15f, e = %20.15f, f = %20.15f\n", d,e,f);

    double g=Third*3.0;
    double h=ThirdFloat*3.0;
    float i=ThirdFloat*3.0f;
    printf("(1/3)*3: g = %20.15lf; h = %20.15lf, i = %20.15f\n", g, h, i);
}

Which gives that output

a =    0.000000000000000, b =    0.333333333333333, c =    0.333333343267441
d =    0.000000000000000, e =    0.333333343267441, f =    0.333333343267441
(1/3)*3: g =    1.000000000000000; h =    1.000000029802322, i =    1.000000000000000

I assume that output for a and d looks like this because compiler casts integer value to float after division. b looks good, e is wrong because of low float precision, so as c and f .

But i have no idea why g has correct value (i thought that 1.0/3.0 = 1.0lf/3.0lf , but then i should be wrong) and why h isn't the same as i .

Let us first look closer: use "%.17e" (approximate decimal) and "%a" (exact).

#define Third (1.0/3.0)
#define ThirdFloat (1.0f/3.0f)
#define FMT "%.17e, %a"
int main(void) {
    double a=1/3;
    double b=1.0/3.0;
    double c=1.0f/3.0f;
    printf("a = " FMT "\n", a,a);
    printf("b = " FMT "\n", b,b);
    printf("c = " FMT "\n", c,c);
    puts("");
    float d=1/3;
    float e=1.0/3.0;
    float f=1.0f/3.0f;
    printf("d = " FMT "\n", d,d);
    printf("e = " FMT "\n", e,e);
    printf("f = " FMT "\n", f,f);
    puts("");
    double g=Third*3.0;
    double h=ThirdFloat*3.0;
    float i=ThirdFloat*3.0f;
    printf("g = " FMT "\n", g,g);
    printf("h = " FMT "\n", h,h);
    printf("i = " FMT "\n", i,i);
}

Output

a = 0.00000000000000000e+00, 0x0p+0
b = 3.33333333333333315e-01, 0x1.5555555555555p-2
c = 3.33333343267440796e-01, 0x1.555556p-2

d = 0.00000000000000000e+00, 0x0p+0
e = 3.33333343267440796e-01, 0x1.555556p-2
f = 3.33333343267440796e-01, 0x1.555556p-2

g = 1.00000000000000000e+00, 0x1p+0
h = 1.00000002980232239e+00, 0x1.0000008p+0
i = 1.00000000000000000e+00, 0x1p+0

But i have no idea why g has correct value

  1. (1.0/3.0)*3.0 can evaluate as a double at compiler or run time and the rounded result is exactly 1.0.

  2. (1.0/3.0)*3.0 can evaluate at compiler or run time using wider than double math and the rounded result is exactly 1.0. Research FLT_EVAL_METHOD .

and why h isn't the same as i.

(1.0f/3.0f) can use float math to form the float quotient that is noticeably different than one-third: 0.333333343267.... a final *3.0 is not surprisingly different that 1.0.

The outputs are all correct. We need to see why the expectation was amiss.


OP further asks: "Why is h ( float * double ) less accurate than i ( float * float )?"

Both start with 0.333333343267... * 3.0 , not one-third * 3.0 .
float * double is more accurate. Both form a product, yet float * float is a float product rounded to the nearest 1 part in 2 24 whereas the more accurate float * double product is a double and rounds to the nearest 1 part in 2 53 . The float * float round to 1.0000000 whereas float * double rounds to 1.0000000298...

But i have no idea why g has correct value (i thought that 1.0/3.0 = 1.0lf/3.0lf

G has exactly the value it should based on:

#define Third (1.0/3.0)    
...
double g=Third*3.0;

which is g=(1.0/3.0)*3.0;
Which is 1.000000000000000 (when printed with "%20.15lf" )

I think i got the answer.

#define Third (1.0/3.0)
#define ThirdFloat (1.0f/3.0f)


    printf("%20.15f, %20.15lf\n", ThirdFloat*3.0, ThirdFloat*3.0);//float*double
    printf("%20.15f, %20.15lf\n", ThirdFloat*3.0f, ThirdFloat*3.0f);//float*float
    printf("%20.15f, %20.15lf\n", Third*3.0, Third*3.0);//double*double
    printf("%20.15f, %20.15lf\n\n", Third*3.0f, Third*3.0f);//float*float

    printf("%20.15f, %20.15lf\n", Third, Third);
    printf("%20.15f, %20.15lf\n", ThirdFloat, ThirdFloat);
    printf("%20.15f, %20.15lf\n", 3.0, 3.0);
    printf("%20.15f, %20.15lf\n", 3.0f, 3.0f);

And output:

   1.000000029802322,    1.000000029802322
   1.000000000000000,    1.000000000000000
   1.000000000000000,    1.000000000000000
   1.000000000000000,    1.000000000000000

   0.333333333333333,    0.333333333333333
   0.333333343267441,    0.333333343267441
   3.000000000000000,    3.000000000000000
   3.000000000000000,    3.000000000000000

First line is not accurate because of the limitations of float. Constant ThirdFloat has really low precision, so when multiplied by double , compiler takes this really bad approximation ( 0.333333343267441 ), converts it into double and multiplies by 3.0 given by double , and that gives also wrong result ( 1.000000029802322 ).

But if ThirdFloat , which is float , is multiplied by 3.0f , which is float as well, compiler can avoid approximation by taking exact value of 1/3 and multiply it by 3 , that's why i got exact result.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM