Search code examples
c++doubleprecision

How to accurately display and store and use data exactly up to certain number of decimal places in C++?


If I have a double variable, I want to store its value in a file to two decimal places with the standard rules of rounding off. I don't just want to store and display data up to two decimal places but I also want to do arithmetic using this data, when I reopen the file, exactly up to two decimal places. In other words when I read this data from the file into a double variable, I want this data to be accurately loaded to two decimal places also, exaclty as I stored it. Using <iomanip> and fixed and setprecision from what I have seen so far are only used for displaying the output to be used with std::cout. I may be wrong though. Please explain using the following example how to accomplish this? Also how to store data from a double variable up to two decimal places in a binary file also?

std::ofstream file("data.txt");
int hours = 8, mins= 35;
char ap='p';

double format_24_hr=0.00;
format_24_hr = static_cast<double>(hours) + 12.00 + static_cast<double>(mins)/60.00;
file<<format_24_hr;  // how to store double to two decimal places in a binary file
file.close();

Also, when I'm reading the file like follows, how do I make sure that it's loading the data to two decimal places exactly as I stored it?

std::ifstream file("data.txt");
double var=0.00;
file>>var;   // would the data read here be exactly loaded to two decimal places as I stored it?
file.close();

Solution

  • What you have described is a fixed point representation. Trying to use a floating point representation for fixed point values often leads to issues, as you have discovered and as described in Is floating point math broken? See Fixed point vs Floating point number for a more detailed comparison of the representations.

    A reasonable (and sometimes simple, depending on your needs) approach is to use an integer to represent your values. You could build an interface around this integer so that it is divided by 100 when viewed, making it look like a number with two digits after the decimal place. That is, if the stored value is 2058, code using that value would see (the nearest double value to) 20.58. Here is an example of a starting point:

    class FixedPoint {
        int value;
    
    public:
        double get() const { return static_cast<double>(value) / 100.0; }
        // Also define other needed functionality, including arithmetic.
        // Multiplication and division require a bit of care.
    };
    

    However your example code shows an attempt to store hours and minutes as fractional hours. This is already problematic because that association is not precise. For example, you cannot get 20.58 hours from an integral number of hours and minutes; you can get close (20.58333333...), but not exactly.

    A more consistent approach than fractional hours would be total minutes. Instead of storing

    static_cast<double>(hours) + 12.00 + static_cast<double>(mins)/60.00
    

    to a file and dealing with precision issues, you could get perfect precision by storing

    (hours + 12) * 60 + mins
    

    This is an integer, side-stepping the question of how many decimal places to use. The exact same value will be restored when the saved value is read.

    Save the fractional hours for human-readable output (if that is needed), and use the total number of minutes for values that need to be stored and retrieved by your program.