1

I'm running into something I've never seen before and would very much like to understand what is going on. I'm trying to round a double to 2 decimal places and cast that value to a string. Upon completion of the cast things go crazy on me.

Here is the code:

void formatPercent(std::string& percent, const std::string& value, Config& config)
{
    double number = boost::lexical_cast<double>(value);
    if (config.total == 0)
    {
        std::ostringstream err;
        err << "Cannot calculate percent from zero total.";
        throw std::runtime_error(err.str());
    }
    number = (number/config.total)*100;
    // Format the string to only return 2 decimals of precision
    number = floor(number*100 + .5)/100;
    percent = boost::lexical_cast<std::string>(number);

    return;
}

I wasn't getting quite what I expected so I did some investigation. I did the following:

std::cout << std::setprecision(10) << "number = " << number << std::endl;
std::cout << "percent = " << percent << std::endl;

...and get the following:

number = 30.63
percent = 30.629999999999999

I suspect that boost is doing something funny. Does anyone have any insight here?

Seriously, how strange is this?!? I ask for 10 digit precision on a double and get 4 digits. I ask to cast those 4 digits to a string and get that mess. What is going on?

Warren P
  • 65,725
  • 40
  • 181
  • 316
Rico
  • 5,692
  • 8
  • 46
  • 63
  • 7
    Is it time for another link to What Every Computer Scientist Should Know About Floating Point Arithmetic: http://download.oracle.com/docs/cd/E19957-01/806-3568/ncg_goldberg.html – Warren P Jun 30 '11 at 19:55
  • 1
    Your link doesn't address my question at all. It's actually just a pedantic way of getting around answering my question. You will notice the answer from Martin Beckett directly answered this question. – Rico Jun 30 '11 at 20:00
  • The expansion of 30.63 in binary is infinite just as the expansion of 1/3 in base 10 is infinite. lexical-cast() does what it is supposed to because number really isn't 30.63. See http://stackoverflow.com/questions/5016464/boostlexical-cast-conversion-double-to-string-c – Apprentice Queue Jun 30 '11 at 20:09
  • 1
    @Apprentice - but the binary representation to 10sf is still 0.6300000000, unless you knew the quirks of setprecision() you would expect it to print this. – Martin Beckett Jul 01 '11 at 03:39
  • @Beckett, the question was why setprecision() worked and percent did not. What was unexpected to Rico wasn't setprecision() but percent. – Apprentice Queue Jul 04 '11 at 03:39

4 Answers4

3

The decimal number 30.63 cannot be stored in an object of type double. The closest valid double value, the one that is actually stored, is 8621578536647393 * 2^-48, which, in decimal notaion, is 30.629999999999999005240169935859739780426025390625.

You can see that if you do std::cout << std::setprecision(100) << "number = " << number << std::endl;

Cubbi
  • 46,567
  • 13
  • 103
  • 169
1

std::precision sets the maximum number of significant digits to display

On the default floating-point notation, the precision field specifies the maximum number of meaningful digits to display in total counting both those before and those after the decimal point. Notice that it is not a minimum and therefore it does not pad the displayed number with trailing zeros if the number can be displayed with less digits than the precision.

30.629999999999999 is the actually floating point representation of 30.63

Martin Beckett
  • 94,801
  • 28
  • 188
  • 263
  • Thank you for this helpful answer. I did not realize std::precision would not show trailing zeros (or 9s). – Rico Jun 30 '11 at 20:04
1

You're asking for ten digits of precision, but the actual "inaccuracy" is further down, so when it's rounded into ten digits it becomes a neat 30.63. Your lexical_cast takes all the digits into account, therefore resulting in the precise floating point value (perhaps not the kind of precision you want, but it's the most precise representation of what's in the computer's memory).

Matti Virkkunen
  • 63,558
  • 9
  • 127
  • 159
0

The problem is that floating point numbers cannot accurately represent arbitrary decimal values. What you're seeing is floating point error. Boost is accurately displaying the value you provide to it.

Peter Ruderman
  • 12,241
  • 1
  • 36
  • 58