Same algorithm, different precision. Why?

In an exercise about floats, I was asked to compile the following algorithm:

1
2
3
4
5
6
float a = 1.0;
int b = 3;
float c = a/b;
float d = (c * b) - 1.0;

cout << d << endl;


Which resulted in the following output: 2.98023e-008. Then, we had to compile the same algorithm, coded in this form instead:

1
2
3
4
float a = 1.0;
int b = 3;

cout << (a/b * b) - 1.0 << endl;


Which resulted in the following output: 0.

I don't understand the difference between the two algorithms, except that one has more written steps than the other.

So, what is the difference?

Thanks
I have gone through your code and compiled it and ran it myself and I am getting 0 for both sets of code.

Is that exactly how they were written?
Disch,

if I understand well, the value of a float might be truncated if it is stored in RAM, and might not be truncated if it is not (might = it depend on my system, hardware, compiler, etc).

In my example (write 1), since I used intermediate steps, some values got stored in RAM and got truncated, hence the loss of precision. In my other example (write 2), since I have no intermediate steps, nothing went into RAM, and nothing got truncated.

Finally, if I understand well the article, there no way of predicting this (the best thing is to avoid RAM usage)?

P.S. Militie, yes, it was written exactly that way ;)

Thank you both for your time!
> In my example (write 1), since I used intermediate steps,
> some values got stored in RAM and got truncated, hence the loss of precision.

In the earlier example, there was a narrowing conversion from double to float, followed by a widening conversion from the resultant float back to double. The narrowing conversion could have resulted in loss of precision.

If instead, we had written
1
2
3
4
5
6
float a = 1.0;
int b = 3;
float double c = a/b;
float double d = (c * b) - 1.0;

cout << d << endl;


There would have been no narrowing conversion; the result may have been different.

Check this out on your implementation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <iomanip>

void print_type(float) { std::cout << "float\n" ; }
void print_type(double) { std::cout << "double\n" ; }

int main()
{
    std::cout << "type of 1.0 is: " ;
    print_type(1.0) ; // type of literal 1.0 is 'double'

    float a = 1.0;
    int b = 3 ;

    // 'usual arithmetic conversions' are applied
    // 1. If either operand is of type long double, the other operand is converted to type long double.
    // 2. If the above condition is not met and either operand is of type double, the other operand is converted to type double.
    // https://msdn.microsoft.com/en-us/library/3t4w2bkb.aspx
    // type of a/b - 1.0  is 'double'
    auto result = a/b * b - 1.0 ; // type of result is 'double'
    std::cout << "type of result is: " ;
    print_type(result) ; // double

    std::cout << std::fixed << std::setprecision(20) << result << '\n' ; // pint the value of the double

    float c = a / b ; // type of c is 'float'
    float d = (c*b) - 1.0 ; // type of d is 'float' ; result of (c*b) - 1.0  (double) is 'narrowed' to a float;
                            // there may be a loss of precision during the narrowing

    // calls std::ostream::operator<< (float)
    // which calls std::num_put<char>::put(double) => std::num_put<char>::do_put(double)
    // float d is widened to a double; the result of this widening may not be precisely equal
    // to the value pf result due to possible loss of precision during the earlier narrowing
    std::cout << d << '\n' ; // print the result of c widened to double

    float c1 = a / b ;
    double d1 = (c1*b) - 1.0 ;
    std::cout << d1 << '\n' ; // the result printed may be different from that printed for float d

    double c2 = a / b ;
    double d2 = (c2*b) - 1.0 ;
    std::cout << d2 << '\n' ; // the result printed may be different from that printed for double d1
}
Topic archived. No new replies allowed.