GotW #67

Home Blog Talks Books & Articles Training & Consulting

On the
blog
RSS feed November 4: Other Concurrency Sessions at PDC
November 3
: PDC'09: Tutorial & Panel
October 26: Hoare on Testing
October 23
: Deprecating export Considered for ISO C++0x

This is the original GotW problem and solution substantially as posted to Usenet. See the book Exceptional C++ Style (Addison-Wesley, 2004) for the most current solution to this GotW issue. The solutions in the book have been revised and expanded since their initial appearance in GotW. The book versions also incorporate corrections, new material, and conformance to the final ANSI/ISO C++ standard (1998) and its Technical Corrigendum (2003).

Double or Nothing 
Difficulty: 4 / 10

No, this issue isn't about gambling. It is, however, about a different kind of "float," so to speak, and lets you test your skills about basic floating-point operations in C and C++.

Problem

JG Question

1. What's the difference between "float" and "double"?

Guru Question

2. Say that the following program takes 1 second to run, which is not unusual for a modern desktop computer:

int main()
{
  double x = 1e8;
  while( x > 0 )
  {
    --x;
  }
}

How long would you expect it to take if you change "double" to "float"? Why?

Solution

1. What's the difference between "float" and "double"?

Quoting from section 3.9.1/8 of the C++ standard:

There are three floating point types: float, double, and long double. The type double provides at least as much precision as float, and the type long double provides at least as much precision as double. The set of values of the type float is a subset of the set of values of the type double; the set of values of the type double is a subset of the set of values of the type long double.

The Wheel of Time

2. Say that the following program takes 1 second to run, which is not unusual for a modern desktop computer:

int main()
{
  double x = 1e8;
  while( x > 0 )
  {
    --x;
  }
}

How long would you expect it to take if you change "double" to "float"? Why?

It will probably take either about 1 second (on a particular implementation floats may be somewhat faster, as fast, or somewhat slower than doubles), or forever, depending whether or not float can exactly represent all integer values from 0 to 1e8 inclusive.

The above quote from the standard means that there may be values that can be represented by a double but that cannot be represented by a float. In particular, on some popular platforms and compilers, double can exactly represent all integer values in [0,1e8] but float cannot.

What if float can't exactly represent all integer values from 0 to 1e8? Then the modified program will start counting down, but will eventually reach a value N which can't be represented and for which N-1 == N (due to insufficient floating-point precision)... and then the loop will stay stuck on that value until the machine on which the program is running runs out of power (due to local power outage or battery life limits), its operating system crashes (more common on some platforms than others), Sol turns out to be a variable star and scorches the inner planets, or the universe dies of heat death, whichever comes first.[1]

A Word About Narrowing Conversions

Some people might wonder: "Well, besides universal heat death, isn't there another problem? The constant 1e8 has type double. So if we just changed 'double' to 'float' the program wouldn't compile because of the narrowing conversion, right?" Well, let's quote standardese again, this time from section 4.8/1:

An rvalue of floating point type can be converted to an rvalue of another floating point type. If the source value can be exactly rep- resented in the destination type, the result of the conversion is that exact representation. If the source value is between two adjacent destination values, the result of the conversion is an implementation- defined choice of either of those values. Otherwise, the behavior is undefined.

This means that a double constant can be implicitly (i.e., silently) converted to a float constant, even if doing so loses precision (i.e., data). This was allowed to remain for C compatibility and usability reasons, but it's worth keeping in mind when you do floating-point work.

A quality compiler will warn you if you try to do something that's undefined behavior, namely put a double quantity into a float that's less than the minimum, or greater than the maximum, value that a float is able to represent. A really good compiler will provide an optional warning if you try to do something that may be defined but could lose information, namely put a double quantity into a float that is between the minimum and maximum values representable by a float, but which can't be represented exactly as a float.

 

 

Notes

1. Indeed, because the program keeps the computer running needlessly, it also needlessly increases the entropy of the universe, thereby hastening said heat death. In short, such a program is quite environmentally unfriendly and should be considered a threat to our species. Don't write code like this.[2]

2. Of course, performing any kind of additional work, whether by humans or machines, also increases the entropy of the universe, thereby hastening heat death. This is a good argument to keep in mind for times when your employer requests extra overtime.

Copyright 2009 Herb Sutter