Formatting Floating Point Numbers

ontrolling the number of fractional digits after the decimal point is a common requirement in various application domains?including dialog boxes, relational databases, financial applications, SMS messages, and when processing data files. This solution shows you how to control the number of fractional digits by using familiar design techniques presented in previous 10-Minute Solutions. Along the way, I will dispel a few common myths about strings and precision.


How can you control the number of fractional digits of floating point values?


Convert the floating-point value to a string and format its textual representation.

Presenting the Problem
The inspiration for writing this article comes from a question that was recently posted on the DevX C++ Forum. The poster asked how to write a function that accepts a long double argument and converts it to a string. The resulting string should contain up to two decimal digits in the fraction part. For example, the floating-point value 123.45678 should yield the string “123.45.” Seemingly, this is a trivial programming task. However, to make this function truly useful, the design has to be flexible enough toallow the caller to specify a different number of fractional digits. In addition, the same function must handle various exceptions gracefully. For example, it should be able to cope with integral values such as 123.0 or 123.

Before we tackle this task, it’s important to remember two design maxims that hold true for every state of the art C++ code base:

  • Maxim #1: Whenever you need to format a numeric value, convert the value to a string. This way, you guarantee that each digit occupies exactly one character.
  • Maxim #2: When you need to convert something to a string, use the library.

The interface of the conversion function is straightforward: the first parameter is the value that needs to be formatted. The second parameter represents the number of decimal digits that should appear after the decimal point. The latter will have a default value. The return value is of type string:

string do_fraction(long double value, int decplaces=3);

Note: The number of decimal places includes the decimal point, therefore a default value of 3 is needed for two fractional digits.

Precision and Order
Naturally, your first step is to convert the long double value to a string. Using the standard C++ library, this task is a cinch. However, there is one whimsical quirk of which you should be aware. For some reason, the stringstream objects have a default precision of 6. Many programmers mistakenly assume that “precision” refers to number of digits in the fraction part. This isn’t true?precision refers to the total number of digits. Thus, the number 1234.56 can be represented safely with a default precision of 6. However, the number 12345.67 will be truncated to 12345.6. So, if you have a larger number, say 1234567.8, the result will be converted silently to the scientific format: 1.23457e+06, which is certainly not what you want. To avoid this nuisance, set the default precision to the maximum before performing any conversion.

To obtain the maximum number of digits that long double can represent, use the library:

string do_fraction(long double value, int decplaces=3){ int prec=  numeric_limits::digits10; // 18 ostringstream out; out.precision(prec);//override the default out<

The numeric value is now stored in str, waiting to be formatted.

Going Dotty
The formatting operation consists of locating the decimal point's position. If the number of fractional digits is higher than decplaces, do_fraction() will remove the remaining digits.

To locate the decimal point, use string::find(). Remember that STL algorithms use an agreed-upon constant to report a "value not found" condition. In the case of string, this constant is called string::npos:

char DECIMAL_POINT='.'; // use ',' in European localessize_t n=str.find(DECIMAL_POINT);if ((n!=string::npos)// is there a decimal point?{ //check the number of fractional digits}

If there's no decimal point, do_format() returns the string as is. Otherwise, do_format() checks whether the number of fractional digits is higher than decplaces. If it is, the fractional part is truncated:

size_t n=str.find(DECIMAL_POINT);if ((n!=string::npos)// is there a decimal point?    &&(str.size()> n+decplaces))//is it followed by at                                //least decplaces digits?//write nul instead of the first digit after decplaces str[n+decplaces]='';  

The last line of code overwrites the first superfluous fractional digit. It uses the constant to chop the string at the right place. Notice, however, that string objects' data may contain nul characters; the actual length of the string is determined by the value returned from size(). Therefore, you can't assume that, at this stage, the string is formatted correctly. In other words, if str contained "123.4567" originally, after the insertion of the constant it contains "123.457". To shrink str to "123.45", use the self-swapping idiom:

str.swap(string(str.c_str()) );//get rid of spurious                               //characters after the nul

How does it work? The string::c_str() function returns a const char * representation of the string object. This value is used as the initializer of a temporary string object. Then, the temporary is used as the argument for str.swap(). This swap() call assigns the value "123.45" to str. Some older compilers that don't support default template arguments might not accept this swap call. In this case, use manual swapping instead:

string temp=str.c_str();str=temp;

This isn't the most elegant code but it gets the job done.

No Strings Attached
Here's the complete code for do_fraction():

string do_fraction(long double value, int decplaces=3){ ostringstream out; int prec=  numeric_limits::digits10; // 18 out.precision(prec);//override default out< n+decplaces)) //is it followed by at                                //least decplaces digits? {  str[n+decplaces]='';//overwrite first redundant digit } str.swap(string(str.c_str()));//get rid of spurious                               //characters after the nul return str;}

If you're wary about returning a string object by value, pass a string object by reference as an argument:

void do_fraction(long double value, string & str, int decplaces=3);

Personally, I prefer to trust my compiler for applying this optimization automatically. In addition, using string as the return value enables you to use do_fraction() in the following manner:

cout << funct(123456789.69999001) << '	' <<  funct(12.011)<

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Overview

Recent Articles:

©2023 Copyright DevX - All Rights Reserved. Registration or use of this site constitutes acceptance of our Terms of Service and Privacy Policy.

Sitemap