Floating points: don’t fix what ain’t broken!


Here’s an example that demonstrate two things. First, casting a double to a float back to a double can’t give you the same result auto-magically. Second, when you have a program that outputs double or single precision to strings, make you sure you don’t try to specify the highest precision yourself, since you might make the user blink an eye.

That’s what happened to me today. We have a program which we don’t directly control the source code and that program only handles double precision floating point values as input and output, but our plugin to that program stores single floating point values for memory and performance concerns. So the user enters numeric value in string format that gets converted to double, then we take that number and store it in our database using floats. Then at some point, the user queries that value later and realize that 0.3 = 0.30000001192092896, blinks an eye, says WTF, come to your desk, tell you that your stuff is all wrong, you cry, you try to fix that thing by doing floating point magic, your magic only works for ONE case, then cry again and then you realized, AH HA… that you can’t do nothing for that poor QA guy that just want 0.3 to equal 0.3!

Here what happened: that program which we don’t control the source, decided to print on the screen using cout << setprecision(18) << double_value; the numeric value. If that program would have used something like sprintf( buf, “%f”, value ) to build the string to print, the user would have got the right result (0.3 = 0.3!), since sprintf would have use round-trip to format the right value.

So all that said, casting from double to float back to double or float to double back to float (or whichever fucking-asking-for-trouble-casting-order) will most of the time never give you the same numeric value, and setting the precision at which you display info to the user won’t serve no one! Also if you do automation with stuff that gets manipulated as such, please don’t compare floating numbers together directly. As an example, you can use the code below to validate your input with the program’s output:

fabs(d1 - d2) < EPSILON ? "equal" : "not equal";

Summary:
1- Don’t do try to do magic with floating point numbers! You won’t serve anyone.
2- Only set the precision for numeric value if YOU KNOW what precision to use!

Here’s a small program that demonstrate what I talk about above.

#include <tchar.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <iostream>
#include <math.h>
#include <iomanip>
#include <sstream>
 
#define ASSERT( expr ) printf("%s is %s\n", #expr, expr ? "true" : "false")
 
const char* manipulate(const double dInput1, const char* input, char* output)
{
  const double dInput2 = strtod( input, NULL );
  const float fInput = static_cast<float>( dInput2 );
  const double dOutput = static_cast<double>( fInput );
 
  std::stringstream ss;
  // < BAD DON'T DO THIS IF YOU DON'T REALLY HAVE A REASON
  ss << std::setprecision(18) << dOutput;
 
  // > DO SOMETHING SIMPLER LIKE THIS THAT WILL HANDLE THE PRECISION MORE NICELY
  sprintf( output, "%f", dOutput );
 
  // I hope it is true!
  ASSERT( dInput1 == dInput2 ); 
 
  // That's why you shouldn't specify the highest precision yourself!
  ASSERT( strcmp(ss.str().c_str(), output) == 0); 
 
  // Can't happen! If you know how, send me a mail!
  ASSERT( dInput2 == dOutput );
 
  return output;
}
 
int _tmain(int argc, _TCHAR* argv[])
{
  char bufferOutput[32] = { '\0' };
 
#define TEST(dvalue) printf( "%s = %s\n", \
                #dvalue, manipulate(dvalue, #dvalue, bufferOutput) )
 
  TEST( 0.3 );
  TEST( 0.123 );
  TEST( 0.10 );
 
  return 0;
}
Output for 0.3:
input   = "0.3"	
output  = "0.300000" // GOOD AND WANTED
ss.c_str()  = "0.30000001192092896" // GOOD BUT UNWANTED
dInput1 = 0.29999999999999999	
dInput2 = 0.29999999999999999	
fInput  = 0.30000001	        
dOutput = 0.30000001192092896	

// As expected, inputs are equal, Ouf!
dInput1 == dInput2 is true

// Strings aren't equal, someone decided to use an unjustified hard-coded precision!
strcmp(ss.str().c_str(), output) == 0 is false

// That's normal, IEEE-754 ain't magic, don't try to fix it, just make sure you print something meaningful to the user!
dInput2 == dOutput is false
This entry was posted in C++, Coding and tagged , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *