Still celebrating National IT Professionals Day with 3 months of free Premium Membership. Use Code ITDAY17

x
Solved

# Need to round decimal stored as a string

Posted on 2004-08-04
Medium Priority
766 Views
I have a std:string with a decimal value.  I want to round this to a certain number of decimal places.

For example, I have "38.987654321" and I want to round it to 3 places, I want to get "38.988".

I can convert this to a float, use the pow function to shift it (38987.654321), add .5, truncate, shift it back, and set it back to a string.

Is there an easier way?
I would like to use a double rather than a float.  How would I do that?

Thanks!

0
Question by:borghard
[X]
###### Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

• Help others & share knowledge
• Earn cash & points
• Learn & ask questions
• 3
• 2

LVL 5

Expert Comment

ID: 11716521
pow will work ok with a double.  However, in my experience, the technique you described produces terrible roundoff error.  THerefore, I created a rounding function using actual strings because I found ecvt to be a cleaner (less round-off error prone) function.

0

LVL 5

Expert Comment

ID: 11716567
DOUBLE_TYPE Round(DOUBLE_TYPE d, short nPlaces)
{
DOUBLE_TYPE dTrunc = 0;

// OK, here's the plan stan:
// first, save the sign of the input number so we don't have to deal with it.

BOOL bIsNeg = FALSE;
if ( d < 0 )
{
bIsNeg = TRUE;
d = -d;
}

// Second, convert the input value to a string.
// note that numbers whose absolute value < 1 will get a leading 0.
// if we are rounding to the tens or higher (negative places), then we
// create the string with an appropriate number of leading zeros by
// shifting the location of the sprintf call.
char szBuf[500];
memset(szBuf,'0',sizeof(szBuf)); // clear buffer so we can run past the generated string!
if ( nPlaces < 0 )
sprintf(szBuf - nPlaces, "%f", d);
else
sprintf(szBuf, "%f", d);
szBuf[sizeof(szBuf)-1]=0;

#ifdef _DEBUG
CString sBefore1stShift = szBuf;
#endif
// set carry flag.
// we are looking for the situation where a round-up will result in the first non-zero
// digit rolling back to zero with a carry forward of a 1 in the next most significant
// position.  This happens when there is a string of nines or zeros from the most
// significant digit to the "decision digit".
// Specifically, this string of digits will always be a solid string of zero or more
// nines immediately left of the decision digit preceeded by a solid string of 0 or more
// zeros.  Any other configuration will "absorb" the round-up operation in the same
// number of digits.
// e.g.  0.998 rounds up to 1.000 when rounded at 0,1 or 2 places.
// e.g.  0.908 rounds up to 0.910 at 2 places since there is a break between
//             the 9s and the decision digit (8)
// e.g.  0.008 rounds up to 0.01 at 2 places, the 9 string is zero bytes long
// note the most significant digit has moves one place to the left.  Therefore, we shift
// the decimal place one position to the right in the output (seen below as adding 1 to
// the "zero" variable containing the decimal position.
//
// Implementation of the carry flag is as follows:
// 1. default to true
// 2. set to false when any of the following occur:
//        a. any digit 1-8 is discovered OR
//        b. if any non-9 digit is encountered AFTER the first 9 digit OR
//        c. if we don't round-up.
// note that the "seen first" flag is reversed when shifting the decimal place to the left
// in the case of tens place rounding (90->100)
//
bool bCarryFwd = true;
bool bSeenFirst9 = false;
// Third, find the decimal point..
int zero = -1;
for(int i = 0; i < lstrlen(szBuf); i++ )
{
bool bIsDecisionDigit = false;
// if rounding to right of the decimal, scan for possible resets to the carry flag.
// e.g. 199.99, 1 should not have the carry flag set because of the 1,
// but that is not discovered in the shift loop below after the period is located

if ( szBuf[i] == '.' )
{
zero = i;  // zero contains the zero-based location of the decimal place...

// fourth, calculate number of places to shift the decimal point so that the
// digit immediately before the decimal point is the decision digit in the
// rounding operation.  This digit is the one being rounded off, so we add
// +1 to get decision digit left of the decimal place.
// e.g. nPlaces = 0, then we are rounding based on the digit immediately
//      after the decimal point so we shift 1
// e.g. nPlaces = -1, rounding tens place based on the ones place, which
//      is already in the correct position.
// e.g. nPlaces = -2, rounding hundreds, so shift tens to ones
// examples:
// value    string        zero (decimal point)    nPlaces        places    dir   string after
// .9        0.90000              1                       0           1     1    09.00000

int places = nPlaces + 1;
if ( places < 0 )
{
TRACE("Rounding to Negative Places!\n");
//ASSERT(0); // stop!
}

// compute direction of shift
int dir = places < 0 ? -1 : 1;

// compute string location of shift (0 based..)
int nStart = zero;
int nEnd = nStart + places;

ASSERT( nEnd >= 0 && nEnd <= sizeof(szBuf)-1 );
if ( !( nEnd >= 0 && nEnd <= sizeof(szBuf)-1 ) )
return 0;

// loop through string for two purposes:
// first, move digits for decimal point, we only do this until we
// reach nEnd.
// second, check digits to set the carry flag.  For this purpose, the
// loop runs until we hit the decimal point...

for( int j = nStart; (dir == 1) ? (j < nEnd) : ( j > nEnd ); j += dir )
{
ASSERT( j >= 0 );
if ( j < 0 )
{
return 0;  // avoid writing in front of string...
}
// if j hasn't reached nEnd yet, copy the byte, else don't move, just let the carry flag checks continue
char c = szBuf[j];
ASSERT( c == '.' );
szBuf[j] = szBuf[j+dir];
szBuf[j+dir] =c;
}
szBuf[nEnd] = '.';

for( j = 0; szBuf[j] != 0 && szBuf[j] != '.'; j++ )
{
if ( szBuf[j+1] == '.' )
{
// don't perform this check on the decision digit...
}
else
{
char cNext = szBuf[j];
if ( cNext >= '1' && cNext <= '8' )
bCarryFwd = false;

if ( cNext == '9' && dir == 1 )    // carry rule b
bSeenFirst9 = true;
else if ( bSeenFirst9 )
bCarryFwd = false;
}
}
#ifdef _DEBUG
CString sAfter1stShift= szBuf;
#endif

// count leading zeros because we are about to loose them...
char* p = szBuf;

for( ; *p == '0'; p++ )

__int64 l = _atoi64(szBuf);

#ifdef _DEBUG
__int64 lBefore = l;
#endif
if ( l % 10 >= 5 )
{
l += 10;
}
else
bCarryFwd = false; // carry rule c
#ifdef _DEBUG
__int64 lAfter = l;
#endif
l /= 10;

#ifdef _DEBUG
__int64 lRound = l;
int zeroBefore = zero;
#endif
if ( bCarryFwd )
zero += 1;    // shift decimal 1 point right.

#ifdef _DEBUG
int zeroAfterCarry = zero;
#endif

sprintf(szBuf, "%I64d00000000000000000", l);

#ifdef _DEBUG
CString sBefore2ndShift = szBuf;
#endif
if ( nLeadingZeros )
{
while (nLeadingZeros > 0 )
}
#ifdef _DEBUG
CString sAfter2ndShift = szBuf;
#endif

// shift digits for decimal point
if ( places+zero+dir < 0 ) // then rounding off out of range (for example round 6 to 100s place)
{
memset(szBuf,'0',sizeof(szBuf)); // clear buffer so we can run past the generated string!
szBuf[sizeof(szBuf)-1]=0;
}
else
{
for ( j = places; j != 0; j -= dir )
{
szBuf[zero+j+dir] = szBuf[zero+j];
}
szBuf[zero+dir] = szBuf[zero];
szBuf[zero]='.';
}
#ifdef _DEBUG
CString sAfter3rdShift = szBuf;
#endif

if ( bIsNeg )
{
memmove(szBuf+1, szBuf, sizeof(szBuf)-1);
szBuf[0] = '-';
szBuf[sizeof(szBuf)-1]=0;
}
dTrunc = atof(szBuf);

#ifdef _DEBUG
//        TRACE("'%f\t'%s\t%d\t%d\t%d\t%d\t'%s\t%d\t%I64d\t%I64d\t%I64d\t%d\t%d\t'%s\t'%s\t'%s\t'%f\n",
//                d, sBefore1stShift, i,nPlaces, places,dir,sAfter1stShift,origLeadingZeros ,lBefore,lAfter,lRound,bCarryFwd,zeroAfterCarry,sBefore2ndShift,sAfter2ndShift,sAfter3rdShift,dTrunc);
#endif
break;
}
}

return dTrunc;
}
0

LVL 5

Accepted Solution

millsoft earned 500 total points
ID: 11716573
BTW, if you're not using MFC you will want to remove my debugging code.

0

LVL 22

Expert Comment

ID: 11717048
If you have a string, and you want to round it, how about something like:

p = strchr( '.', Num );
if( p >= 0 ) p += 3;  // point to last digit we want;

q = p + 1;
if( Num[ q ] <= '4' )    { }; //keep current last digit
else
{ //Increment digit and carry
r = p;
while( Num[r] == '9' ) Num[r++] = '0';
Num[ r ]++;
}
}

0

Author Comment

ID: 11717604
doing it the grg99 seems like it would be fast but I would march the other way, correct (r--)?
This does not care for rounding 99.99999 2 decimal places to 100.00
I think it would give me 99.00
0

LVL 22

Expert Comment

ID: 11719662
OOPS, right, you have to go down to propagate the carries.
Also you have to skip the '.'.  and if you fall off the left end, you have to insert a "1".

(And of course plunk down a '\0' past the last digit you want.)

0

## Featured Post

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Often, when implementing a feature, you won't know how certain events should be handled at the point where they occur and you'd rather defer to the user of your function or class. For example, a XML parser will extract a tag from the source code, whâ€¦
C++ Properties One feature missing from standard C++ that you will find in many other Object Oriented Programming languages is something called a Property (http://www.experts-exchange.com/Programming/Languages/CPP/A_3912-Object-Properties-in-C.htâ€¦
The goal of the video will be to teach the user the concept of local variables and scope. An example of a locally defined variable will be given as well as an explanation of what scope is in C++. The local variable and concept of scope will be relatâ€¦
The viewer will learn how to use the return statement in functions in C++. The video will also teach the user how to pass data to a function and have the function return data back for further processing.
###### Suggested Courses
Course of the Month6 days, 23 hours left to enroll

#### 705 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.