 Rounding Numbers and Roundoff ErrorsDecember 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 roundoff 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 roundoff 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 roundoff 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 roundoff 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 roundoff 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 supercomputer 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 roundoff 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 
 Author  Reply  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

 Solitaire (no login)  *Excellent! Thank you.  December 10 2002, 1:47 PM 
 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

 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 

 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

 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 

 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

 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 

 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 halfway number is rounded up.
You can implement rounding halfway 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

 Clippy (Login burger2227) R  * The point here is that both rounding methods are fine!  November 14 2007, 3:48 PM 
 Moneo (no login)  * Great! We agree on something.  November 14 2007, 7:56 PM 
 Clippy (Login burger2227) R  * In a Round about way LOL  November 14 2007, 8:38 PM 
 Solitaire (Login Solitaire1) Moderator  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;ENUS;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.

 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

 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

 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

 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

 Solitaire (Login Solitaire1) Moderator  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 

 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
 
   

