Solved

Need to round decimal stored as a string

Posted on 2004-08-04
6
758 Views
Last Modified: 2008-02-01
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
Comment
Question by:borghard
  • 3
  • 2
6 Comments
 
LVL 5

Expert Comment

by:millsoft
Comment Utility
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

by:millsoft
Comment Utility
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;
            int nLeadingZeros=0;

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

            __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;
            int origLeadingZeros = nLeadingZeros;
#endif
            if ( nLeadingZeros )
            {
                memmove(szBuf+nLeadingZeros , szBuf, sizeof(szBuf)-nLeadingZeros );
                while (nLeadingZeros > 0 )
                    szBuf[--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("value\tstring\tzero\tnPlaces\tplaces\tdir\t1stShift\tleading zeros\tl\tlAfter\tlRound\tbCarry\tzac\tsb2ndShift\tsa2ndShift\tsa3rdShift\tResult\n");
    //        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

by:
millsoft earned 125 total points
Comment Utility
BTW, if you're not using MFC you will want to remove my debugging code.

Brad
0
6 Surprising Benefits of Threat Intelligence

All sorts of threat intelligence is available on the web. Intelligence you can learn from, and use to anticipate and prepare for future attacks.

 
LVL 22

Expert Comment

by:grg99
Comment Utility
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

by:borghard
Comment Utility
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

by:grg99
Comment Utility
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

How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

Join & Write a Comment

This article shows you how to optimize memory allocations in C++ using placement new. Applicable especially to usecases dealing with creation of large number of objects. A brief on problem: Lets take example problem for simplicity: - I have a G…
Container Orchestration platforms empower organizations to scale their apps at an exceptional rate. This is the reason numerous innovation-driven companies are moving apps to an appropriated datacenter wide platform that empowers them to scale at a …
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.
The viewer will learn additional member functions of the vector class. Specifically, the capacity and swap member functions will be introduced.

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

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

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

10 Experts available now in Live!

Get 1:1 Help Now