QB / QB64 Discussion Forum      Other Subforums, Links and Downloads
 


  << Previous Topic | Next Topic >>Return to Index  

Rounding Numbers and Round-off Errors

December 8 2002 at 12:13 PM
Solitaire  (no login)

 
Some time ago I discovered that QBasic will round out numbers ending with .5 to the nearest EVEN integer, or any fractional number ending with 5 to the nearest EVEN least significant digit up to the limit of the type range. For example, if you declare a number AS INTEGER and assign it the value of 3.5, it will become 4, and if you assign it the value of 4.5, it will also become 4. Similarly, if you assign a Single (or default type) number the value of .12342335 it will print as .1234234 and .12342345 will also print as .1234234. The same will happen with default Single numbers 1234.2335 and 1234.2345. The CINT() function will also round out to the nearest even integer. Try this:

CLS
DIM num1 AS INTEGER, num2 AS INTEGER
num1 = 3.5
num2 = 4.5
num3 = .12342335#
num4 = .12342345#
num5 = 1234.2335#
num6 = 1234.2345#
num7 = 3.5
num8 = 4.5
PRINT num1
PRINT num2
PRINT num3
PRINT num4
PRINT num5
PRINT num6
PRINT CINT(num7)
PRINT CINT(num8)
END


The same is true for the Round() function in Visual Basic 6. I was wondering if this was some kind of bug, until I checked the Internet and searched for Rounding Numbers. There seems to be a difference of opinion on how numbers ending with .5 are to be rounded. Most of us would round UP to the next highest number, as we were taught in mathematics classes. However, the preferred way is to round to the nearest EVEN number! I suppose this is used as the preferred method in order to avoid significant round-off errors. Look at it this way: Groups of numbers ending with 1 - 4 and groups of numbers ending with 6 - 9 would statistically be about the same. That leaves the odd group of numbers ending with the digit 5. If they were all to be rounded UP, then eventually, if thousands of numbers are totalled, The result would be too big, producing a large round-off error. On the other hand, if half of the numbers ending with 5 were rounded down and half rounded up, the error would not be as great. With individual or small groups of numbers, however, you would probably prefer to use the always round UP method.

I checked the Excel spreadsheet program to see how the rounding function worked there, and to my surprise, it always rounded UP if a number ended with 5. I decided to write a program with a function that will round out any number, positive or negative to any place selected by the user, using the round UP method same as in Excel. If the number is too big or too small for the largest data type, it will produce an error, so I included an errortrap to end the function if that happens. The code for this program appears below.

You should be aware that a round-off error may still occur if the results you require use the same number of places after the decimal as the number of places you have rounded out. Knowing how to round out a number is not the same as solving a round-off error; just the opposite. Eventually the missing fraction lost in multiple roundouts compounds the error and produces a result which is no longer accurate to the required degree. To avoid round-off errors, you must attempt to extend the number of decimal places as far as possible before rounding out.

The problem is that the computer is a finite machine and can only produce results to a finite degree of accuracy. After a certain number of decimal places have been reached within the memory limits of the data type, the computer rounds out the last digit automatically. Using a data type with the greatest range from minimum to maximum helps extend the accuracy. The SINGLE data type is accurate only to 7 significant digits. The DOUBLE data type is accurate to 15 significant digits. The digit which would follow that number has been rounded out. If you want to use a number with greater accuracy, the newer version of Visual Basic includes a Decimal type, which is accurate to 28 digits. But some numbers are infinite, and no matter how powerful a computer you use, even if you use a super-computer which can handle numbers up to a couple of hundred places, the last place will still have to be rounded out.

The point is, you have to know what your requirements are, and how many significant digits are needed in order to produce the desired result without incurring a round-off error. If it's greater than the number available with QBasic's data types, then it simply will not produce accurate results. An undeclared variable in QBasic is SINGLE by default. If you have not declared your variables by type, then you can increase the accuracy of your results by declaring your number variable as DOUBLE. Put this statement at the beginning of your program, using the name of your variable for the number. Also see my code for the RoundUP function, which follows.

DIM number AS DOUBLE

--------------------------------------------------

DECLARE FUNCTION RoundUp# (num AS DOUBLE, place AS INTEGER)
DIM num AS DOUBLE, place AS INTEGER, splace AS STRING, E AS STRING
DO
CLS
PRINT "Program will round to nearest digit for specified number ";
PRINT "of decimal places,"
PRINT "or will round to least significant digit of an integer."
PRINT "It will work with either a positive or negative number."
PRINT "Zero places will round to nearest integer."
PRINT : INPUT "Enter number to round: ", num
PRINT : PRINT "Enter number of places to round after the decimal,"
PRINT "negative number of places to left of decimal, or 0 for "
INPUT "rounded integer (place must be between -9 and 9.): ", splace$
place = VAL(splace)
IF place >= -9 AND place <= 9 THEN
PRINT : PRINT "Rounded number is "; RoundUp(num, place)
ELSE
PRINT "Out of range."
END IF
PRINT : INPUT "Enter for another number or Q to quit: ", E$
LOOP UNTIL UCASE$(E$) = "Q"
END

FUNCTION RoundUp# (num AS DOUBLE, place AS INTEGER)
DIM decnum AS DOUBLE, decplace AS LONG
IF place > 9 OR place < -9 THEN EXIT FUNCTION 'otherwise incorrect
IF place >= 0 THEN
decplace = 10 ^ place
decnum = num * decplace
IF decnum > 0 THEN
decnum = INT(decnum + .5)
ELSE
decnum = FIX(decnum - .5)
END IF
RoundUp = decnum / decplace
ELSEIF place < 0 THEN
place = ABS(place)
decplace = 10 ^ place
decnum = num / decplace
IF decnum > 0 THEN
decnum = INT(decnum + .5)
ELSE
decnum = FIX(decnum - .5)
END IF
RoundUp = decnum * decplace
END IF
END FUNCTION

 
 Respond to this message   
AuthorReply
Earthborn
(no login)

Very nice! But could be shortened though...

December 10 2002, 1:02 PM 

How about this:

FUNCTION Round (Num, Decimals)
n = Num * (10 ^ Decimals)
n = SGN(n) * (ABS(INT(n + .5)))
Round = n / (10 ^ Decimals)
END FUNCTION

 
 Respond to this message   
Solitaire
(no login)

*Excellent! Thank you.

December 10 2002, 1:47 PM 


 
 Respond to this message   
Moneo
(no login)

BEWARE! Earthborn's function looked nice until I tested it >>>

November 7 2007, 6:21 PM 

The following is Earthborn's original function:

FUNCTION Round (Num, Decimals)
n = Num * (10 ^ Decimals)
n = SGN(n) * (ABS(INT(n + .5)))
Round = n / (10 ^ Decimals)
END FUNCTION

The error lies in this statement:
n = SGN(n) * (ABS(INT(n + .5)))

The intent of the ABS is to feed an absolute value to the INT, because INT works differently for negative numbers.

The corrected function follows:

FUNCTION Round (Num, Decimals)
n = Num * (10 ^ Decimals)
R = SGN(N)*INT(ABS(N)+.5) '*** FIXED ***
Round = n / (10 ^ Decimals)
END FUNCTION

NOTE: Earthborn's function did not declare the rounding method used. It is Symmetric Arithmetic Rounding, the most commonly used method.

Regards..... Moneo







 
 Respond to this message   
Clippy
(Login burger2227)
R

Really?

November 12 2007, 1:55 PM 

INT(4.1) = 4
INT(4.9) = 4
INT(-4.1) = -5
INT(-4.9) = -5

I think your forgetting that -5 is lower than -4. INT always returns a lower value until it increases to the next integer value, which is one higher.

ABS is used to keep the resulting number positive.

FIX will raise the value one on negative numbers and is the same as INT on positive numbers. Try that if you feel the need.

Ted

PS: You waited 5 years to unearth this startling revelation? I will notify Solitaire who probably has been happily using it all of that time.


    
This message has been edited by burger2227 on Nov 12, 2007 2:11 PM


 
 Respond to this message   
Moneo
(no login)

Ted, don't be so critical. Listen to the following:

November 12 2007, 5:55 PM 

How the INT function works for positive and negative numbers, I am well aware.

The point of the post that I started was the testing of Earthborn's function. He didn't say, but from looking at the code one must assume that the rounding being done is the standard Symmetric Arithmetic Rounding (SAR) method.

Testing his fonction as is, you will get:
-1.5 rounds to -1, where the SAR method gives -2.
-2.5 rounds to -2, where the SAR method gives -3.

It's remotely possible that he was implementing some other rounding method, but if so, he certainly should have declared which method.

My correction to his code will perform rounding according to the SAR method.

Regards..... Moneo


 
 Respond to this message   
Clippy
(Login burger2227)
R

OK, explain this then

November 12 2007, 7:31 PM 

I took Earthborn's and your functions and made two of my own:

DECLARE FUNCTION RMoneo! (Num!, Decimals!)
DECLARE FUNCTION Round! (Num!, Decimals!)
DECLARE FUNCTION RTed! (Num!, Decimals!)
INPUT "Enter a number: ", Num
Decimals = 2



COLOR 12: PRINT Round(Num, Decimals)

COLOR 14: PRINT RMoneo(Num, Decimals)

COLOR 10: PRINT RTed(Num, Decimals)

FUNCTION RMoneo (Num, Decimals)
n = Num * (10 ^ Decimals)
n = SGN(n) * INT(ABS(n) + .5)'*** FIXED *** I changed R to n
RMoneo = n / (10 ^ Decimals)
END FUNCTION

FUNCTION Round (Num, Decimals)
n = Num * (10 ^ Decimals)
n = SGN(n) * (ABS(INT(n + .5)))
Round = n / (10 ^ Decimals)
END FUNCTION

FUNCTION RTed (Num, Decimals)
n = Num * (10 ^ Decimals)
n = INT(n + .5)
RTed = n / (10 ^ Decimals)
END FUNCTION

FUNCTION RFix (Num, Decimals)
n = Num * (10 ^ Decimals)
n = FIX(n + .5)
RFix = n / (10 ^ Decimals)
END FUNCTION


I tried all four and Earthborn's and mine were identical even with negative numbers. With just 2 decimal places I tried 11.999 and our's rounded to 12 while yours stayed 11.999. Then I noticed an R where n should be in your post. Now they seem to be almost the same, but yours goes lower sometimes with negative numbers. I don't think that was the original intent of Earthborn's post however.

You will notice that I removed all refernces to SGN(n) and ABS. The sign came up correctly on mine despite that. So who really needs them? SGN saves the number's sign which is NEVER multiplied by a negative number in ANY of the functions. Thus ABS(n) CREATES the sign problem to begin with! Your's works for what you desired I imagine. So how can you call his formula any worse than our's? Solitaire liked it that way. I also made a function with FIX to see if that made a difference. Your's was the only one different!

**********************************************
Rounding Down as explained by Microsoft

The simplest form of rounding is truncation. Any digits after the desired precision are simply ignored. The Fix() function is an example of truncation. For example, Fix(3.5) is 3, and Fix(-3.5) is -3.

The Int() function rounds down to the highest integer less than the value. Both Int() and Fix() act the same way with positive numbers - truncating - but give different results for negative numbers: Int(-3.5) gives -4.

>>>> The Fix() function is an example of symmetric rounding because it affects the magnitude (absolute value) of positive and negative numbers in the same way. The Int() function is an example of asymmetric rounding because it affects the magnitude of positive and negative numbers differently.
***********************************************
Since your rounding actually increases the negative value, it cannot be Symmetrical rounding!

Obviously you never used the IDE, because there are N's and n's and that strange R. Use the IDE friend. I copy my code straight from a module using Notepad AFTER I run it in the IDE.

Regards, Ted


    
This message has been edited by burger2227 on Nov 13, 2007 11:55 AM


 
 Respond to this message   
Moneo
(no login)

Ted, thanks for fixing my coding error >>>

November 13 2007, 3:30 PM 

I lifted the code from a working program of mine, and didn't test it again because it was already working. "Famous last words." The variable names were different. Sorry.

In the Microsoft document
http://support.microsoft.com/kb/196652/
that you quoted from, there's a table further down with the heading of "Sample Data". In this table you will notice that the column for Asymmetric Arithmetic and the column for Symmetric Arithmetic demonstrate the same differences between Earthborn/your rounding functions and my rounding function. The differences are:
For -2.5, yours gives -2, and mine -3
For -1.5, yours gives -1, and mone -2
For 0.5, yours gives 0, and mine -1.

My algorithm performs Symmetric Arithmetic Rounding, and the SGN and ABS are absolutely necessary. I don't understand why you don't accept this. This Symmetric Arithmetic algorithm has been discussed over the last 5 years on several QB forums as well as on the MathForum.

From the results that I see for Earthborn and your algoritms, it seems like they are performing Asymmetric Arithmetic Rounding. Having never worked with this rounding method, I can't say for sure.

We still don't know if Earthborn was implementing Symmetric of Asymmetric Arithmetic Rounding. Testing shows that the results will be the same for both methods, except for negative numbers that end in .5. If he or Solitaire never tested with this combination of values, their tests would have been satisfactory for them.

Thanks for all the work and effort that you put into this issue.

You're probably right about the IDE, but, "you can't teach an old dog new tricks."

Regards..... Moneo



 
 Respond to this message   
Clippy
(Login burger2227)
R

Dear OLD Dog ..........................

November 13 2007, 8:36 PM 

If you look at the code, you will notice that Earthborn added .5 to the number, no matter what value it was. That tells me that he was rounding up. If your rounding method rounds up then your theory is perfectly Symmetric.

However, the rounding done, with all of the ABS, SGN and INT stuff still rounds lower than what it could have been. Your negatives increase, the negative numbers and just do not make much sense to me, but who am I to tell you it is wrong?

I know one thing, you will NEVER get any money from me to invest. If I am losing money and round off like you, I will be double penalized!

Ted

PS: You just chastized me for picking on Antoni and you post BEWARE? Like it will mess up your PC? Common, buddy you should have just said something like "Symmetrical Rounding".


    
This message has been edited by burger2227 on Nov 13, 2007 8:42 PM


 
 Respond to this message   
Moneo
(no login)

Symmetric Arithmetic Rounding accoroding to Microsoft document

November 14 2007, 9:08 AM 

Ted,
Here's an extract from the Arithmetic Rounding section of the Microsoft document on rounding.

"However, what about 1.5, which is equidistant between 1 and 2? By convention, the half-way number is rounded up.
You can implement rounding half-way numbers in a symmetric fashion, such that -.5 is rounded down to -1, or in an asymmetric fashion, where -.5 is rounded up to 0."

Notice that for Symmetric Arithmetic Rounding,
-.5 is rounded to -1.
and therefore implies that:
-1.5 is rounded to -2
-2.5 is rounded to -3.

Now, if Symmetric Arithmetic Rounding does not meet with your rounding requirements, you can chose to use some other rounding method. See the "Sample Data" table in the Microsoft document for examples of how other rounding methods perform as opposed to Symmetric Arithmetic Rounding.

Regards..... Moneo




 
 Respond to this message   
Clippy
(Login burger2227)
R

* The point here is that both rounding methods are fine!

November 14 2007, 3:48 PM 


 
 Respond to this message   
Moneo
(no login)

* Great! We agree on something.

November 14 2007, 7:56 PM 

*

 
 Respond to this message   
Clippy
(Login burger2227)
R

* In a Round about way LOL

November 14 2007, 8:38 PM 


 
 Respond to this message   
Solitaire
(Login Solitaire1)
R

Information about arithmetic rounding vs. banker's rounding

September 6 2004, 5:21 PM 

This is a Microsoft Knowledge Base Article:

http://support.microsoft.com/default.aspx?scid=kb;EN-US;196652


It's not specific to QB but it does refer to several functions that are also available in QB and work the same way as described.


 
 Respond to this message   
Mac
(Login Mac36)

Is there a better way to simulate CINT?

October 22 2004, 11:48 AM 

We know that CINT(x) will fail unless x is integer size.

Here is a function that will handle larger values. Can you find a better way?

(Just replace the code of CDBLx# with your better way and post)

Mac

DECLARE FUNCTION CDBLx# (k#)
CLS
PRINT "Proof the function works"
k$ = "34.4": GOSUB Testit
k$ = "34.5": GOSUB Testit
k$ = "34.6": GOSUB Testit
k$ = "35.4": GOSUB Testit
k$ = "35.5": GOSUB Testit
k$ = "35.6": GOSUB Testit
SYSTEM

Testit:
PRINT k$; "  "; CDBLx#(VAL(k$)), CDBLx#(VAL("888888" + k$))
PRINT k$; "  "; CDBLx#(VAL("-" + k$)), CDBLx#(VAL("-888888" + k$))
RETURN

FUNCTION CDBLx# (k#)
Max% = 32767
IF ABS(k#) < Max% THEN
  r# = CINT(k#)
ELSE
  'Need to simulate CINT
  k$ = STR$(k#)
  y = INSTR(k$, ".")
  IF y = 0 THEN
    r# = k#
  ELSE
    k1$ = LEFT$(k$, y - 1): k2$ = RIGHT$(k$, LEN(k$) - y + 1)
    r# = VAL(k1$)
    IF r# < 0 THEN
      IF k2$ = ".5" THEN
        IF INSTR("02468", RIGHT$(k1$, 1)) = 0 THEN r# = r# - 1
      ELSE
        IF VAL(k2$) > .5 THEN r# = r# - 1
      END IF
    ELSE
      IF k2$ = ".5" THEN
        IF INSTR("02468", RIGHT$(k1$, 1)) = 0 THEN r# = r# + 1
      ELSE
        IF VAL(k2$) > .5 THEN r# = r# + 1
      END IF
    END IF
  END IF
END IF
CDBLx# = r#
END FUNCTION


 
 Respond to this message   
Buff
(no login)

Re: Is there a better way to simulate CINT?

October 23 2004, 4:05 PM 

Amount#=12345.789123
Dollar&=Amount#
Cent&=(Amount#-Dollar&)*100

print using "##,###,###.##";Dollar&+Cent&/100

 
 Respond to this message   
Buff1
(no login)

Re: Is there a better way to simulate CINT?

October 23 2004, 4:20 PM 

BTW if you want to store it compressed in a file

Amount#=12345.678912
Dollar&=Amount#
Cent&=(Amount#-Dollar&)*100
if Cent&<0 then
Dollar&=Dollar&-1
Cent&=Cent&+100
end if
st$=MKL$(Dollar&)+CHR$(Cent&)

makes a string 5 characters containing up to
+-2.1 billion +2 digit decimal.

then unpack with:

Dollar&=CVL(mid$(st$,1,4))
Cent&=Asc(mid$(st$,5,1) - or just mid$(st$,5)

print Dollar&+Cent&/100


 
 Respond to this message   
Mac
(Login Mac36)

Hey, Buff1 (and Solitaire)

October 24 2004, 6:14 PM 

How about taking my original program and throwing out the stuff between
FUNCTION CDBLx# (k#)
and
END FUNCTION

Then provide your better algorithm. Debug it and post. That way it could be tested and compared.

Thanks

Mac



 
 Respond to this message   
Solitaire
(Login Solitaire1)
R

My version of banker's rounding with large numbers using CINT

October 24 2004, 2:46 PM 

I didn't look at Mac's code. This was done from scratch. I used string manipulation to separate the ones place digit and fractional part from the rest of the number. Converted and rounded the small number with CINT. Concatenated a "0" to the rest of the number before converting it back to a double, then added it to the small rounded number in the ones place.

Basically, since banker's rounding only operates on the digit in the one's place (using the digits to the right of the decimal as the argument), that is all that's really needed, and CINT is adequate for that purpose. The remaining digits to the left of the one's place stay the same no matter how big the number is.

I did this in a hurry but I'm sure it can be converted into a function. First, test it to make sure there's no error.


DIM newnum AS DOUBLE, pnum AS DOUBLE, rnum AS SINGLE
DIM dot AS INTEGER, inum AS INTEGER
DIM snum AS STRING, cnum AS STRING, lnum AS STRING
CLS : INPUT "Enter a number to round:  ", snum$
dot = INSTR(snum$, ".")
IF dot = 0 OR dot = 1 THEN
    cnum$ = snum$       'ones digit + fraction
ELSE
    cnum$ = MID$(snum$, dot - 1)
    lnum$ = LEFT$(snum$, dot - 2)   'left digits up to tens
END IF
IF dot > 0 THEN
    rnum = VAL(cnum$)       'convert ones & fraction to number
    inum = CINT(rnum)       'rounded and changed to integer
    lnum$ = RTRIM$(lnum$) + "0" 'put 0 back in ones place for whole number
    pnum# = VAL(lnum$)       'convert whole number
    newnum# = pnum + inum    'add whole number to rounded ones place
ELSE
    newnum# = VAL(snum$)
END IF
PRINT "Rounded whole number is  "; newnum#
SYSTEM



    
This message has been edited by Solitaire1 on Oct 24, 2004 6:08 PM


 
 Respond to this message   
Solitaire
(no login)

My banker's rounding function integrated with Mac's main program

October 25 2004, 11:06 PM 

DECLARE FUNCTION CDBLx# (k#)
CLS
PRINT "Proof the function works"
k$ = "34.4": GOSUB Testit
k$ = "34.5": GOSUB Testit
k$ = "34.6": GOSUB Testit
k$ = "35.4": GOSUB Testit
k$ = "35.5": GOSUB Testit
k$ = "35.6": GOSUB Testit
SYSTEM

Testit:
PRINT k$; "  "; CDBLx#(VAL(k$)), CDBLx#(VAL("888888" + k$))
PRINT k$; "  "; CDBLx#(VAL("-" + k$)), CDBLx#(VAL("-888888" + k$))
RETURN

FUNCTION CDBLx# (k#)
DIM pnum AS DOUBLE, rnum AS SINGLE
DIM dot AS INTEGER, inum AS INTEGER
DIM snum AS STRING, cnum AS STRING, lnum AS STRING

snum$ = STR$(k#)
dot = INSTR(snum$, ".")
IF dot = 0 OR dot = 1 THEN
    cnum$ = snum$                   'ones digit + fraction
ELSE
    cnum$ = MID$(snum$, dot - 1)
    lnum$ = LEFT$(snum$, dot - 2)   'left digits up to tens place
END IF
IF dot > 0 THEN
    rnum = VAL(cnum$)               'convert ones & fraction to number
    IF k# < 0 THEN rnum = -rnum     'negative number
    inum = CINT(rnum)               'rounded and changed to integer
    lnum$ = RTRIM$(lnum$) + "0"     'put 0 back in ones place for whole number
    pnum# = VAL(lnum$)              'convert whole number
    CDBLx# = pnum + inum            'add whole number to rounded ones place
ELSE
    CDBLx# = num#                   'no fraction given to round
END IF
END FUNCTION


 
 Respond to this message   
 
< Previous Page 1 2 3 Next >
  << Previous Topic | Next Topic >>Return to Index  
 Copyright © 1999-2014 Network54. All rights reserved.   Terms of Use   Privacy Statement  

Newbies usually go to www.qbasic.com and click on The QBasic Forum
Forum regulars have their own ways, which include The QBasic Community Forums