Solved

Need to round decimal stored as a string

Posted on 2004-08-04
6
762 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
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

by:millsoft
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;
            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
ID: 11716573
BTW, if you're not using MFC you will want to remove my debugging code.

Brad
0
Free Tool: ZipGrep

ZipGrep is a utility that can list and search zip (.war, .ear, .jar, etc) archives for text patterns, without the need to extract the archive's contents.

One of a set of tools we're offering as a way to say thank you for being a part of the community.

 
LVL 22

Expert Comment

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

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

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

Free Tool: Postgres Monitoring System

A PHP and Perl based system to collect and display usage statistics from PostgreSQL databases.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

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

Templates For Beginners Or How To Encourage The Compiler To Work For You Introduction This tutorial is targeted at the reader who is, perhaps, familiar with the basics of C++ but would prefer a little slower introduction to the more ad…
Go is an acronym of golang, is a programming language developed Google in 2007. Go is a new language that is mostly in the C family, with significant input from Pascal/Modula/Oberon family. Hence Go arisen as low-level language with fast compilation…
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 be introduced to the member functions push_back and pop_back of the vector class. The video will teach the difference between the two as well as how to use each one along with its functionality.

809 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