11

I have always wondered what happens in case a double reaches it's max value, so I decided to write this code:

#include <stdint.h>
#include <iostream>
#define UINT64_SIZE 18446744073709551615
int main() {
    std::uint64_t i = UINT64_SIZE;
    double d1 = ((double)(i+1)) / UINT64_SIZE; 
    double d2 = (((double)(i)) / UINT64_SIZE)*16;
    double d3 = ((double)(i * 16)) / UINT64_SIZE;

    std::cout << d1 << " " << d2 << " " << d3; 
}

I was expecting something like this:

0 16 0 

But this is my output:

0 16 1 

What is going on here? Why are the values of d3 and d1 different?

EDIT:

I decided to change my code to this to see the result:

#include <stdint.h>
#include <iostream>
#define UINT64_SIZE 18446744073709551615
int main() {
    std::uint64_t i = UINT64_SIZE;
    double d1 = ((double)(i+1.0)) / UINT64_SIZE; //what? 
    double d2 = (((double)(i)) / UINT64_SIZE)*16;
    double d3 = ((double)(i * 16.0)) / UINT64_SIZE;

    std::cout << d1 << " " << d2 << " " << d3; 
}

The result I get now is this:

1 16 16 

However, shouldn't d1 and d3 still be the same value?

2
  • 1
    in d1 and d3 you're looking at integer overflow, replace 1 with 1.0 and 16 with 16.0 Commented Jan 5, 2017 at 18:27
  • Yes, I can undestand that. My question is why are the values of d1 and d3 different. Wouldn't they both overflow and return 0? Commented Jan 5, 2017 at 18:28

2 Answers 2

14

double overflows by loosing precision, not by starting from 0 (as it works with unsigned integers)

d1

So, when you add 1.0 to very big value (18446744073709551615), you're not getting 0 in double, but something like 18446744073709551610 (note last 10 instead of 15) or 18446744073709551620 (note last 20 instead of 15), so - less significant digit(s) are rounded.

Now, you're dividing two almost identical values, result will be either 0.9(9)9 or 1.0(0)1, as soon as double cannot hold such small value - again it looses precision and rounds to 1.0.

d3

Almost the same, when you multiple huge value by 16 - you're getting rounded result (less significant digits are thrown away), by diving it - you're getting "almost" 16, which is rounded to 16.

Sign up to request clarification or add additional context in comments.

1 Comment

My mistake, I didn't actually know this. I guess you learn new things every day!
4

This is a case of loss of precision. Consider the following.

#include <stdint.h>
#include <iostream>

#define UINT64_SIZE 18446744073709551615

int main() {
    std::uint64_t i = UINT64_SIZE;

    auto a = i;
    auto b = i * 16;
    auto c = (double)b;
    auto d = (uint64_t)c;

    std::cout << a << std::endl;
    std::cout << b << std::endl;
    std::cout << c << std::endl;
    std::cout << d << std::endl;

    return 0;
}

On my system the output is as follow.

18446744073709551615
18446744073709551600
1.8446744073709552e+19
9223372036854775808

double simply doesn't have enough precision in this case.

Edit: There is also a rounding problem. When you preform the division with UINT64_SIZE the denumerator is promoted to double and you are left with a decimal value between 0.0 and 1.0. The decimals are not rounded off. The actual value is very near 1.0 and is rounded up when pushed to std::cout.

In your question you ask "what happens in case a double reaches it's max value". Note that in the example you provided no double is ever near it's maximum value. Only it's precision is exceeded. When a double's precision is exceeded, the excess precision is discarded.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.