3

I'm wishing to round up a float variable to two decimal places regardless of what the float is. My current code is shown below:

void MainMethods::convert(float& floatValue, string numericString) 
{
    floatValue = stof(numericString); //converts string to a float
    floatValue = ceil(floatValue * 100.00) / 100.00;
}

Which just converts the string to a float and then after will round up using ceil(). This works fine for when I have either whole numbers, or numbers that go beyond 3 decimals. However when inputting a value of 2 decimals, such as 567.56, the value then returns 567.57 instead. How do I stop this?

  • 1
    Do you need to, or can you simply using `iomanip` tools to properly format the float on output? – Chris Jan 03 '22 at 20:00
  • 1
    Remember that not all numbers can be accurately represented in floating point. Odds are 567.56 merely looks like 567.56 and is actually 567.56000000001 or something like that. If you only ever and always want two decimal points, you may be better off using fixed point arithmetic, in this case storing an integer 100 times the size, performing all arithmetic on this number and converting it only when you need to display it. – user4581301 Jan 03 '22 at 20:05
  • Well in fairness, there is iomanip tools that do display the float and automatically round it either up or down. However as this is part of a bank account like system I was thinking it'd make more sense to have the amount be rounded up only, which setprecision doesn't seem to be doing. Hence trying to use ceil(). – RadioCode345 Jan 03 '22 at 20:07
  • Also not you converted to doubles for a while, if you want everything to stay in floats you should use 100.0f. I tried reproducing in MSVC but the issue isn't there. Debugger shows value of 567.559998 for both steps. – Pepijn Kramer Jan 03 '22 at 20:08
  • 1
    Note: Not all floating point numbers can be represented exactly. If you must have an exact number keep it as an integer and then add a special printing function to add the decimal place in the correct position. – Martin York Jan 03 '22 at 20:08
  • @RadioCode345 For banking systems your better of using fixed point variables. Tom scott has a nice video about why 1/10 + 2/10 != 3/10 : https://www.youtube.com/watch?v=PZRI1IfStY0. Its pretty light to watch but educational nonetheless :) – Pepijn Kramer Jan 03 '22 at 20:09
  • 1
    You *can* use floating point to represent money in a bank account, but only once before getting fired. – n. m. could be an AI Jan 03 '22 at 20:10
  • And that firing might be a bit more literal than you'd prefer. People get very weird and potentially violent when their money gets rounded off in a direction that doesn't favour them. Oddly they seem to be just fine when it rounds in their favour. Humans are weird. – user4581301 Jan 03 '22 at 20:19
  • @user4581301 QED. – Pepijn Kramer Jan 03 '22 at 20:21
  • @user4581301 Lawsuits can occur if it's shown that floating point rounding is being used in favor of the bank or financial institution and against the customer. Floating point was strictly forbidden where I used to work. – PaulMcKenzie Jan 03 '22 at 20:46
  • There is a reason why many banks use COBOL with fixed point calculations! Otherwise you can count the amounts in cents, and only add the decimal point in the output. – BoP Jan 03 '22 at 22:10
  • @BoP -- Usage of COBOL at the place I used to work was preferred over (at the time) `C`, because of the rounding issues. Unless the `C` code used a proprietary library that handled money values accurately, it was all COBOL. – PaulMcKenzie Jan 04 '22 at 14:08

1 Answers1

7

The problem you are seeing is because floating point values can not represent all non integer values exactly. What Every Computer Scientist Should Know About Floating-Point Arithmetic

What you have to remember is that all bits that represent values between 1 and 0 are still powers of 2 (though negative powers).

2^-1    => 0.5
2^-2    => 0.25
2^-3    => 0.125
2^-4    => 0.0625
etc.

Thus to represent an floating point value you need to keep adding these smaller and smaller values until you get close to the value you want.

Looking at your floating point Number:

567.57

Float remainder   Rep         Value
.57               => 2^-1  => 0.5
.07               => 2^-4  => 0.0625
.0075             => 2^-8  => 0.00390625
.00359375         => 2^-9  => 0.001953125
.001640625        etc..

Note: That is not exactly how it is done but to demonstrate the issue.

As a result, you will get rounding issues whenever you use any type of floating point value. This is particularly noticeable when you print the value in base 10 on some stream, but it happens for every operation.

Because you are using 2 decimal places I assume you are trying to represent some form of monetary value?

I would keep the number as an integer.
That way you can represent it exactly. Then you can add a printing function to add the decimal place.

#include <string>
#include <iostream>
#include <iomanip>

class Money
{
    long  value;   // in cent.
    public:
        Money(std::string const& value)
            : value(stof(numericString) * 100)
        {}
        friend std::ostream& operator<<(std::ostream& stream, Money const& money)
        {
            stream << (money.value / 100) << "."
                   << std::setw(2) << std::setfill('0') << (money.value % 100);
            return stream;
       }
};

int main()
{
    Money   myCurrentAccount("1000000.56");

    std::cout << myCurrentAccount << "\n";
}
user4581301
  • 33,082
  • 7
  • 33
  • 54
Martin York
  • 257,169
  • 86
  • 333
  • 562
  • Recommendation: Add [overloads to `Money` for common mathematical operators](https://stackoverflow.com/questions/4421706/what-are-the-basic-rules-and-idioms-for-operator-overloading) as required. – user4581301 Jan 03 '22 at 20:25
  • This can still result in rounding errors even with relatively low numbers though. e.g. `167772.17` being rounded to `167772.18` due to floating-point inaccuracy ([godbolt example](https://godbolt.org/z/fzY1713v8)) - so for accurate results you'll also need to implement the parsing manually, e.g. [godbolt example](https://godbolt.org/z/3zjbjxzME) – Turtlefight Jan 03 '22 at 21:52