Announcement

Collapse
No announcement yet.

Common Rounding

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Common Rounding

    I wrote a function which performs "common rounding" on an extended precision value:

    Code:
    FUNCTION RoundCommon##(rdVal##, BYVAL rlDec&)
      LOCAL lShift&, lSign&, qCalc&&
      lShift& =  CHOOSE&(rlDec&, 100, 1000, 10000, 100000, 1000000)
      lSign&  =  SGN(rdVal##)
      qCalc&& =  CQUD(FIX(rdVal## * lShift&))
      qCalc&& =  ABS(qCalc&&)
      qCalc&& =  qCalc&& + 5
      qCalc&& =  qCalc&& * lSign&
      qCalc&& =  qCalc&& \ 10
      lShift& =  CHOOSE&(rlDec&, 10, 100, 1000, 10000, 100000)
      FUNCTION = CEXT(qCalc&& / lShift&)
    END FUNCTION
    Do you see any obvious optimization potential (apart from writing this code in assembly language)?

    Thank you for any suggestions!

    Heinz Salomon
    Last edited by Heinz Salomon; 29 Sep 2008, 09:31 AM. Reason: signature

  • #2
    Hi,

    I do not have a better function, but a question on rounding in USA !

    here in germany wie round down (like int(x) when x>0)
    from 0 to 4 down, from 5 to 9 up

    -> 2 digits for money:

    456.3649 -> 456.36
    456.3650 -> 456.37

    -56.3649 -> -56.36
    -56.3650 -> -56.37

    a few days ago someone told me, that in the USA rounding would be different:

    0 to 5 down, 6 to 9 up - is this true ?

    Bye
    Hubert
    Last edited by Hubert Brandel; 29 Sep 2008, 09:49 AM. Reason: writing errors
    Regards,
    Hubert

    ------------------------------------
    http://familie-brandel.de/index_e.html

    Comment


    • #3
      I live in the US and I always learned it as >5 is round up.

      http://en.wikipedia.org/wiki/Rounding
      <b>George W. Bleck</b>
      <img src='http://www.blecktech.com/myemail.gif'>

      Comment


      • #4
        Hi,

        now I am confused ... the link say

        common rounding
        Code:
        if  digit >= 5 then 
           UP
        else 
           DOWN
        endif
        The

        Round-to-even method
        Code:
        if  digit > 5 then 
           UP
        elseif digit < 5 then 
           DOWN
        else
           chouse the next evens ... 
           5.0000000... DOWN
           5.0000001... UP
        endif
        did you mean >= or realy >5 ?
        Last edited by Hubert Brandel; 29 Sep 2008, 10:01 AM.
        Regards,
        Hubert

        ------------------------------------
        http://familie-brandel.de/index_e.html

        Comment


        • #5
          Hubert and Heinz, this is no exaggeration, several of us spent the better part of 2 months writing a common rounding routine, and again no exaggeration I've thought about it on and off for another three or so months. Here's my conclusion: This function works 100% as long as you know what your input values are, that is, they are known values or expressions, not unknown floating point expressions, and it is good to 18 digits. And by the way, 5.0000000... rounds up. Heinz, feel free to check your algo against ours for accuracy. I'll try yours too when I can. There is a version 4 too, but it is mostly in asm and a couple beta versions I didn't test fully.

          Code:
          FUNCTION roundUpV3(a AS EXT, b AS LONG) AS EXT
          '---------------------------------------------------------------------------------------
          'rounds "a" to "b" digits past decimal point, but up on 5 instead of using
          'banker's rounding. It rounds as you would expect by just looking at the decimal numbers,
          'as if the computer were a decimal computer. eg. roundUpV3(123.4545, 3) becomes 123.455.
          '---------------------------------------------------------------------------------------
             STATIC x AS EXT
             STATIC qa AS QUAD
             STATIC fillOnce, sizeA AS LONG
             DIM ten(18) AS STATIC QUAD
             IF a < 0 THEN         'handle negatives
                x = ROUND(a, b)    'in 8.04 (and probably most recent CC ver.) round to even, or banker's rounding
                IF x <= a THEN     'rounded up, so no need to go thru hoops.
                   FUNCTION = x
                   EXIT FUNCTION
                END IF
             ELSE
                x = ROUND(a, b)    'in 8.04 (and probably most recent CC ver.) round to even, or banker's rounding
                IF x >= a THEN     'rounded up
                   FUNCTION = x
                   EXIT FUNCTION
                END IF
             END IF
                                   'make powers of 10 to avoid exponent calcs
             IF fillOnce = 0 THEN
             ten(00) = 1
             ten(01) = 10
             ten(02) = 100
             ten(03) = 1000
             ten(04) = 10000
             ten(05) = 100000
             ten(06) = 1000000
             ten(07) = 10000000
             ten(08) = 100000000
             ten(09) = 1000000000
             ten(10) = 10000000000
             ten(11) = 100000000000
             ten(12) = 1000000000000
             ten(13) = 10000000000000
             ten(14) = 100000000000000
             ten(15) = 1000000000000000
             ten(16) = 10000000000000000
             ten(17) = 100000000000000000
             ten(18) = 1000000000000000000
             fillOnce = 1
             END IF
                                  'how big is a?
             SELECT CASE ABS(a)
                CASE < ten(00): sizeA = 01: qa = a * ten(18)
                CASE < ten(01): sizeA = 02: qa = a * ten(17)
                CASE < ten(02): sizeA = 03: qa = a * ten(16)
                CASE < ten(03): sizeA = 04: qa = a * ten(15)
                CASE < ten(04): sizeA = 05: qa = a * ten(14)
                CASE < ten(05): sizeA = 06: qa = a * ten(13)
                CASE < ten(06): sizeA = 07: qa = a * ten(12)
                CASE < ten(07): sizeA = 08: qa = a * ten(11)
                CASE < ten(08): sizeA = 09: qa = a * ten(10)
                CASE < ten(09): sizeA = 10: qa = a * ten(09)
                CASE < ten(10): sizeA = 11: qa = a * ten(08)
                CASE < ten(11): sizeA = 12: qa = a * ten(07)
                CASE < ten(12): sizeA = 13: qa = a * ten(06)
                CASE < ten(13): sizeA = 14: qa = a * ten(05)
                CASE < ten(14): sizeA = 15: qa = a * ten(04)
                CASE < ten(15): sizeA = 16: qa = a * ten(03)
                CASE < ten(16): sizeA = 17: qa = a * ten(02)
                CASE < ten(17): sizeA = 18: qa = a * ten(01)
                CASE ELSE
                   FUNCTION = a
                   EXIT FUNCTION             'exit because a is too big to round
             END SELECT
             IF sizeA + b > 18 THEN
                FUNCTION = a
                EXIT FUNCTION                'exit because round digits too big
             END IF
          
             qa = qa \ ten(18 - (sizeA + b)) 'make whole EXT effectively an integer
             sizeA = qa MOD 10
             IF sizeA = 5 THEN               'is last digit a 5?
                FUNCTION = ((qa + 10) \ 10) / ten(b)'if so round up prev. digits and replace decimal point in correct position
             ELSEIF sizeA = -5 THEN          'or -5
                FUNCTION = ((qa - 10) \ 10) / ten(b)'if so round up prev. digits and replace decimal point in correct position
             ELSE
                FUNCTION = x
             END IF
          
          END FUNCTION

          Comment


          • #6
            Heinz, I ran some tests and your function returned occasional errors. This was the problem we experienced several months ago--an algorithm would seem good, then errors would show up. We'd post a fix, and other errors would appear, and on and on until Guinness recorded the thread as the longest in the history of the internet. So, you may want to consider roundUpV3. It is moderately fast, handles 18 digits, and has had a ton of testing by several PB'ers.

            Here are a few of the errors in roundCommon():
            Code:
            ext val     = 305836372081947.55 round digits = 1
            roundCommon = 305836372081947.5
            roundUpV3   = 305836372081947.6
            ext val     = 5052478737236737.55 round digits = 1
            roundCommon = 5052478737236737.5
            roundUpV3   = 5052478737236737.6
            ext val     = 9830868917420.82595 round digits = 4
            roundCommon = 9830868917420.8259
            roundUpV3   = 9830868917420.826
            ext val     = 10937049177568.15 round digits = 1
            roundCommon = 10937049177568.1
            roundUpV3   = 10937049177568.2
            ext val     = 5273843846123.20805 round digits = 4
            roundCommon = 5273843846123.208
            roundUpV3   = 5273843846123.2081
            ext val     = 89655156198343.4155 round digits = 3
            roundCommon = 89655156198343.415
            roundUpV3   = 89655156198343.416
            ext val     = 87225388707924.7075 round digits = 3
            roundCommon = 87225388707924.707
            roundUpV3   = 87225388707924.708
            ext val     = 4419172709427.19405 round digits = 4
            roundCommon = 4419172709427.194
            roundUpV3   = 4419172709427.1941
            ext val     = 2647177321133404.15 round digits = 1
            roundCommon = 2647177321133404.1
            roundUpV3   = 2647177321133404.2
            ext val     = 49036207327374.0515 round digits = 3
            roundCommon = 49036207327374.051
            roundUpV3   = 49036207327374.052
            ext val     = 4933067151369.03745 round digits = 4
            roundCommon = 4933067151369.0374
            roundUpV3   = 4933067151369.0375
            ext val     = 6186623783298.8815 round digits = 3
            roundCommon = 6186623783298.881
            roundUpV3   = 6186623783298.882
            ext val     = 1087885000.888785 round digits = 5
            roundCommon = 1087885000.88878
            roundUpV3   = 1087885000.88879
            ext val     = 5656671723178.33375 round digits = 4
            roundCommon = 5656671723178.3337

            Comment


            • #7
              Thank you very much for your help by testing my code and publishing your tried and tested common rounding routine.

              Again, the PB forums have helped me in getting a better programmer.

              Heinz Salomon

              Comment


              • #8
                I believe I would base a rounding algorithm to the Round-to-even method...
                (see http://en.wikipedia.org/wiki/Rounding)


                Round-to-even method

                This method is also known as unbiased rounding, convergent rounding, statistician's rounding, Dutch rounding, Gaussian rounding, or bankers' rounding. It is identical to the common method of rounding except when the digit(s) following the rounding digit starts with a five and has no non-zero digits after it. The new algorithm is:

                * Decide which is the last digit to keep.
                * Increase it by 1 if the next digit is 6 or more, or a 5 followed by one or more non-zero digits.
                * Leave it the same if the next digit is 4 or less
                * Otherwise, if all that follows the last digit is a 5 and possibly trailing zeroes; then change the last digit to the nearest even digit. That is, increase the rounded digit if it is currently odd; leave it if it is already even.

                With all rounding schemes there are two possible outcomes: increasing the rounding digit by one or leaving it alone. With traditional rounding, if the number has a value less than the half-way mark between the possible outcomes, it is rounded down; if the number has a value exactly half-way or greater than half-way between the possible outcomes, it is rounded up. The round-to-even method is the same except that numbers exactly half-way between the possible outcomes are sometimes rounded up—sometimes down.

                Although it is customary to round the number 4.5 up to 5, in fact 4.5 is no nearer to 5 than it is to 4 (it is 0.5 away from both). When dealing with large sets of scientific or statistical data, where trends are important, traditional rounding on average biases the data upwards slightly. Over a large set of data, or when many subsequent rounding operations are performed as in digital signal processing, the round-to-even rule tends to reduce the total rounding error, with (on average) an equal portion of numbers rounding up as rounding down. This generally reduces upwards skewing of the result.

                Round-to-even is used rather than round-to-odd as it reduces rounding to a final digit of 5, and so reduces the likelihood of error resulting from double rounding.

                Examples:

                * 3.016 rounded to hundredths is 3.02 (because the next digit (6) is 6 or more)
                * 3.013 rounded to hundredths is 3.01 (because the next digit (3) is 4 or less)
                * 3.015 rounded to hundredths is 3.02 (because the next digit is 5, and the hundredths digit (1) is odd)
                * 3.045 rounded to hundredths is 3.04 (because the next digit is 5, and the hundredths digit (4) is even)
                * 3.04501 rounded to hundredths is 3.05 (because the next digit is 5, but it is followed by non-zero digits)

                [edit]

                Comment


                • #9
                  Originally posted by Charles Dietz View Post
                  I believe I would base a rounding algorithm to the Round-to-even method...
                  And indeed, that is just what the PB ROUND() function does. Good ol' banker's rounding.

                  (However, given the financial condition of some of these banks the past few weeks, maybe we should scrutinize this rounding philosophy. )
                  Last edited by John Gleason; 30 Sep 2008, 06:27 PM. Reason: too many "should"'s were in there :)

                  Comment


                  • #10
                    John, with the algorithm that I posted from Wikipedia, 3.045 should not round up, but yours does... you're rounding all ending 5's up. And so does the PowerBasic ROUND function.
                    Last edited by Charles Dietz; 30 Sep 2008, 03:53 PM.

                    Comment


                    • #11
                      Charles, I show ROUND() giving 3.04 for the below. Maybe the problem is that you used a double or single constant for 3.045. To be sure you get the exact EXT precision representation, use either VAL("3.045") or as Paul Purvis showed, x = 3045 / 1000.
                      Code:
                      #COMPILE EXE
                      #DIM ALL
                      
                      FUNCTION PBMAIN () AS LONG
                      
                          LOCAL x AS EXT
                          x = VAL("3.045")
                          x = ROUND(x, 2)
                          ? STR$(x, 18)
                      
                      END FUNCTION

                      Comment


                      • #12
                        >Maybe the problem is that you used a double or single constant for 3.045.

                        3.045 mostly likely was some kind of FLOAT (SINGLE, DOUBLE and EXT are floats), meaning by the time you see it via PRINT or STR$ or FORMAT$ it could have already been rounded.

                        You can avoid floats by using CUR or CUX data types if they hold your range of values.

                        MCM
                        Michael Mattias
                        Tal Systems (retired)
                        Port Washington WI USA
                        [email protected]
                        http://www.talsystems.com

                        Comment


                        • #13
                          John, You're absolutely right. This is what I did...

                          LOCAL x AS EXT
                          x = 3.045
                          ? STR$(x, 18) --> 3.04500007629394531

                          which of course is why it rounds up.

                          Sorry I'm a little slow to catch on to this thread... but why would anyone want a common rounding technique when banker's rounding is less biased and preserves a sense of symmetry, and is what the ROUND function already delivers?

                          Comment


                          • #14
                            One reason is accountants use common rounding. Not too long ago I was going to post the above algo in a new thread called "Accountants, not Bankers," a line from a rounding question asked a few years ago. But someone--actually I think MCM --said it's probably best to have a different title not so confusing. Anyway, I never did start that thread because I couldn't bring myself to use the word "rounding" after that record huge rounding thread.

                            But it's true, I've seen posts asking for, and have been asked myself to write code to round up on 5 for accountants.

                            Comment


                            • #15
                              Interesting... Thanks, John

                              Comment


                              • #16
                                Originally posted by Charles Dietz View Post
                                This is what I did...
                                LOCAL x AS EXT
                                x = 3.045
                                ? STR$(x, 18) --> 3.04500007629394531
                                which of course is why it rounds up.
                                If you write
                                x = 3.045##
                                STR$(x, 18) return exactly 3.045
                                I think when we fill a variable with a constant, we should always define the constant with same data type as the variable.
                                Marc - France

                                Comment


                                • #17
                                  If you write
                                  x = 3.045##
                                  STR$(x, 18) return exactly 3.045
                                  You or I could do the math but I would not be surprised if STR$(X, 23) - were that supported - were to return some non-zero digits in those extra five positions.

                                  Floats are often approximations.

                                  This particular scenario is just begging for:
                                  Code:
                                   LOCAL X AS CUR 
                                   X = [email protected]
                                  CUR and CUX are scaled binary rather than float, so they are not approximations.

                                  MCM
                                  Michael Mattias
                                  Tal Systems (retired)
                                  Port Washington WI USA
                                  [email protected]
                                  http://www.talsystems.com

                                  Comment

                                  Working...
                                  X