Click here to Skip to main content
15,893,508 members
Articles / Programming Languages / C
Article

Some handy decimal functions for double

Rate me:
Please Sign up or sign in to vote.
3.22/5 (10 votes)
20 Feb 2008CPOL1 min read 36.8K   23   5
Rounding, decimal places, etc..

Introduction

I use the functions in math.h a lot. But, some functions are missing, so I wrote my own. They are simple functions dealing with decimal places. Nothing big and fancy. But they can be handy sometimes.

Using the code

The functions are straightforward and self-explaining. These functions depend on math.h (and math.h only).

C++
#include "math.h"

Round() rounds a double to a certain number of decimal places. It is very similar to Excel's Round function.

C++
// rounds a double variable to nPlaces decimal places
double Round(double dbVal, int nPlaces /* = 0 */)
{
    const double dbShift = pow(10.0, nPlaces);
    return  floor(dbVal * dbShift + 0.5) / dbShift; 
}

GetDecimalPlaces() counts and returns the number of decimal places of a double (refer to note 1).

C++
// get the number of decimal places
int GetDecimalPlaces(double dbVal)
{
    static const int MAX_DP = 10;
    static const double THRES = pow(0.1, MAX_DP);
    if (dbVal == 0.0)
        return 0;
    int nDecimal = 0;
    while (dbVal - floor(dbVal) > THRES && nDecimal < MAX_DP)
    {
        dbVal *= 10.0;
        nDecimal++;
    }
    return nDecimal;
}

GetFmtStr() makes a printf()-style format string.

C++
// get the number format string
const char* GetFmtStr(char* szFmt, int nDecimal)
{
    sprintf(szFmt, "%%.%dlf", nDecimal);
    return szFmt;
}

Finally, RoundUD() rounds up or rounds down a double to its nearest value in dbUnit steps.

C++
// round up/down to certain units
double RoundUD(bool bRoundUp, double dbUnit, double dbVal)
{
    static const int ROUND_DP = 10;
    double dbValInUnit = dbVal / dbUnit;
    dbValInUnit = Round(dbValInUnit, ROUND_DP);
    if (bRoundUp) // round up
        dbValInUnit = ceil(dbValInUnit);
    else // round down
        dbValInUnit = floor(dbValInUnit);
    return (dbValInUnit * dbUnit);
}

You can test the functions above with the following code:

C++
printf("rounding %lf d.p.=%d is %lf\n", 10.0,        1,    Round(10.0,         1));
printf("rounding %lf d.p.=%d is %lf\n", 0.0,         0,    Round(0.0,          1));
printf("rounding %lf d.p.=%d is %lf\n", 0.1,         1,    Round(0.1,          1));
printf("rounding %lf d.p.=%d is %lf\n", 0.01,        1,    Round(0.01,         1));
printf("rounding %lf d.p.=%d is %lf\n", 0.0123456,   0,    Round(0.0123456,    0));
printf("rounding %lf d.p.=%d is %lf\n", 0.0123456,   1,    Round(0.0123456,    1));
printf("rounding %lf d.p.=%d is %lf\n", 0.0123456,   2,    Round(0.0123456,    2));
printf("rounding %lf d.p.=%d is %lf\n", 0.0123456,   6,    Round(0.0123456,    6));
printf("rounding %lf d.p.=%d is %lf\n", -0.0123456,  6,    Round(-0.0123456,   6));
printf("rounding %lf up unit=%lf is %lf\n",   0.0,   0.05, RoundUD(true, 0.05, 0.0));
printf("rounding %lf down unit=%lf is %lf\n", 0.0,   0.05, RoundUD(false, 0.05, 0.0));
printf("rounding %lf up unit=%lf is %lf\n",   0.01,  0.05, RoundUD(true, 0.05, 0.01));
printf("rounding %lf down unit=%lf is %lf\n", 0.01,  0.05, RoundUD(false, 0.05, 0.01));
printf("rounding %lf up unit=%lf is %lf\n",   -0.01, 0.05, RoundUD(true, 0.05, -0.01));
printf("rounding %lf down unit=%lf is %lf\n", -0.01, 0.05, RoundUD(false, 0.05, -0.01));
printf("%lf has %d d.p.\n", 0.0,     GetDecimalPlaces(0.0));
printf("%lf has %d d.p.\n", 10.0,    GetDecimalPlaces(10.0));
printf("%lf has %d d.p.\n", -10.0,   GetDecimalPlaces(-10.0));
printf("%lf has %d d.p.\n", 10.123,  GetDecimalPlaces(10.123));
printf("%lf has %d d.p.\n", -10.123, GetDecimalPlaces(-10.123));
printf("%lf has %d d.p.\n", 10.01,   GetDecimalPlaces(10.01));
printf("%lf has %d d.p.\n", -10.01,  GetDecimalPlaces(-10.01));
char szFmt[32];
printf(GetFmtStr(szFmt, GetDecimalPlaces(0.0)), 0.0); printf("\n");
printf(GetFmtStr(szFmt, GetDecimalPlaces(10.0)), 10.0); printf("\n");
printf(GetFmtStr(szFmt, GetDecimalPlaces(10.010)), 10.010); printf("\n");
printf(GetFmtStr(szFmt, GetDecimalPlaces(-0.0345)), -0.0345); printf("\n");

The output will be something like:

rounding 10.000000 d.p.=1 is 10.000000
rounding 0.000000 d.p.=0 is 0.000000
rounding 0.100000 d.p.=1 is 0.100000
rounding 0.010000 d.p.=1 is 0.000000
rounding 0.012346 d.p.=0 is 0.000000
rounding 0.012346 d.p.=1 is 0.000000
rounding 0.012346 d.p.=2 is 0.010000
rounding 0.012346 d.p.=6 is 0.012346
rounding -0.012346 d.p.=6 is -0.012346
rounding 0.000000 up unit=0.050000 is 0.000000
rounding 0.000000 down unit=0.050000 is 0.000000
rounding 0.010000 up unit=0.050000 is 0.050000
rounding 0.010000 down unit=0.050000 is 0.000000
rounding -0.010000 up unit=0.050000 is 0.000000
rounding -0.010000 down unit=0.050000 is -0.050000
0.000000 has 0 d.p.
10.000000 has 0 d.p.
-10.000000 has 0 d.p.
10.123000 has 3 d.p.
-10.123000 has 3 d.p.
10.010000 has 2 d.p.
-10.010000 has 2 d.p.
0
10
10.01
-0.0345

Points of Interest

  • Note 1: GetDecimalPlaces() is worth some explanation. In a loop, the double is multiplied by 10 and then compared to its floor value (truncated value). If they are the same, it means we have exhausted the decimal places. Comparison between doubles is tricky because the internal representation of a double is not exact. So, the comparison must allow certain margins of error. The margin is specified by THRES, which is set to 1-10.

History

  • First version: 20 Feb 2008.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Hong Kong Hong Kong
I don't know anything about Lisp, Ruby, D, EJB and J++.

Comments and Discussions

 
QuestionBug? Pin
AvidFan28-Jun-12 1:01
AvidFan28-Jun-12 1:01 
AnswerRe: Bug? Pin
Larry Harding24-Jul-12 4:04
Larry Harding24-Jul-12 4:04 
GeneralFloating Point Explorer Pin
Mike O'Neill26-Feb-08 13:59
Mike O'Neill26-Feb-08 13:59 
It's possible that this article fails to articulate the distinction between the stored value of a number and its displayed value. For example, it's not possible to round a floating point number to a designated number of decimal places (except for trivial cases), for the reason that floating point numbers are binary numbers, not decimal numbers. The rounded number will always differ by some small amount from the desired exact result.

You can, however, display the number so that it appears to have the exactly correct value, even though it's not.

As one quick example, consider the decimal number of 1/10 = 0.10 exactly, in base 10. In binary (base 2), it is simply impossible to express this number exactly, for the reason that it is a repeating binary sequence. In binary, the closest approximation to 1/10 is 0.0001100110011.... , with the "0011" part repeating forever.

Of course, even a double can only store 64 bits, so eventually we will need to truncate the repeating "0011" part. And when we truncate, we depart from the desired true value of exactly 1/10 =0.10 (base 10). It should therefore be clear that we can try all we want, but it will never be possible to store a binary number which represents the deciaml value of 0.10 exactly.

But with proper formatting options, we can display the stored number so that the displayed value "looks like" the desired result, even though the stored value is different from the displayed value.

See Joseph M. Newcomer's "Floating Point Explorer" for one example that attempts to clarify the distinction between a number's stored value and its displayed value: http://www.flounder.com/floating_point_explorer.htm[^]
GeneralRe: Floating Point Explorer Pin
S.C.Wong27-Feb-08 15:06
S.C.Wong27-Feb-08 15:06 
GeneralIncidentally ... Pin
Mike O'Neill27-Feb-08 15:48
Mike O'Neill27-Feb-08 15:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.