Rounding the numeric expression x.
f = Round(x [,n])
f = FRound(x [,n])
f = QRound(x [, n])
f | : floating point variable |
x | : any numeric variable |
n | : integer |
In all aspects of operation, Round and FRound are identical: when the optional parameter n is omitted, they round x to the nearest whole integer, with the decimal 0.5 rounded up; where n is positive, they round to n decimal points, usually rounding up if the next decimal is a '5', but sometimes rounding down (see example); and where n is negative, to the nearest integer multiple of 10-n, rounded up once again if the next digit is a '5'.
QRound uses the 80x87 coprocessor instruction for rounding and, thus, acts like so: when the optional parameter n is omitted, it rounds x to the nearest EVEN integer; where n is positive, it acts like Round and rounds to n decimal points, usually rounding up if the next decimal is a '5', but sometimes rounding down (see example); and where n is negative, to the nearest integer multiple of 10-n where the pertinent digit is EVEN.
The differences are illustrated in the example below (remember Round acts in the same way as FRound).
Debug.Show
Trace QRound(100.5) // Output: 100
Trace FRound(100.5) // Output: 101
Debug
Trace QRound(101.5) // Output: 102
Trace FRound(101.5) // Output: 102
Debug
Trace QRound(100.55, 1) // Output: 100.5 (next 5 rounded down)
Trace FRound(100.55, 1) // Output: 100.5 (next 5 rounded down)
Debug
Trace QRound(100.555, 2) // Output: 101.5 (next 5 rounded up)
Trace FRound(100.555, 2) // Output: 101.5 (next 5 rounded up)
Debug
Trace QRound(105, -1) // Output: 100
Trace FRound(105, -1) // Output: 110
Debug
Trace QRound(115, -1) // Output: 120
Trace FRound(115, -1) // Output: 120
The behaviour of Round/FRound when dealing with rounding to decimal places is odd and inconsistent: it should follow the pattern set and round up if the next digit is a '5', which it usually does, but not always. To get around this problem, you can use the following rather complicated workaround below to ensure these functions always round up in this situation:
Debug.Show
Local Int32 n = 1
Trace FRound(100.55, n) // Output: 100.5 (next 5 rounded down)
Trace FRound(100.55 + (1 * 10 ^ -(n + 1)), n) // Output: 100.6 (next 5 rounded up)
Instead of using the above workaround - which is by far the quickest - either of the following routines below can be used to replace the function altogether:
Debug.Show
Test 100.5
Test 3.675, 2
Test 100.55, 1
Test 104, -1 // only works properly with RoundX
Test 105.99, -1 // only works properly with RoundX
Procedure Test(n#, Optional p%)
Local t# = Timer : Trace RoundX(n#, p%) : Trace Timer - t#
t# = Timer : Trace RoundX2(n#, p%) : Trace Timer - t#
EndProcedure
Function RoundX(v#, Optional p%, Optional ds As Variant)
Local a$ = Trim(Str(v#)), dp$, dp%, p1%, p2% // Create string of passed number
If IsMissing(ds) : dp$ = Left(Mode(Format), 1) // If not passed, then get local decimal separator
Else : dp$ = Trim(ds) // Else use passed separator
EndIf
dp% = InStr(a$, dp$) // Find the position of the decimal separator
If dp% = 0 Then a$ = a$ & dp$ : dp% = InStr(a$, dp$) // If no separator, add one and recalculate position
a$ = Left(a$, dp% - 1) & Mid(a$, dp% + 1) // Remove separator to facilitate the next few operations
If p% < 0 And (-p%) > (dp% - 1) // If places negative and less figures than places in front of separator
a$ = String((-p%) - (dp% - 2), "0") & a$ : dp% = InStr(a$, dp$) // ...then add extra zeroes and recalculate position
EndIf
If p% > 0 Then If p% > Len(a$) - dp% Then Return v# // If places positive and more than there are decimal figures then return passed number
p1% = Val(Left(a$, dp% + p% - 1)) // Get figures that will be retained...
p2% = Val(Mid(a$, dp% + p%, 1)) // ...then the next figure which will not
If p2% >= 5 Then Inc p1% // If "next" figure 5 or greater, then increase "retained" figures by one
a$ = Trim(p1%) // Convert "retained" figures back to a string
If p% < 0 Then a$ = a$ & String(-p%, "0") // If negative places, then add extra zeroes to the end
If p% > 0 Then a$ = Left(a$, dp% - 1) & dp$ & Mid(a$, dp%) // If positive places, then reinsert decimal separator
Return Val(a$) // Return rounded value
EndFunction
Function RoundX2(v#, Optional p%) // Quicker but does not work when p% is negative
Return Val(Format(v#, "0." & String(p%, "#"))) // Use the inbuilt Format function to round and return the result
EndFunction
Ceil(), CInt, Frac(), Fix(), Floor(), Int(), Trunc()
{Created by Sjouke Hamstra; Last updated: 04/07/2022 by James Gaite}