Round, FRound and QRound Functions

Purpose

Rounding the numeric expression x.

Syntax

f = Round(x [,n])
f = FRound(x [,n])
f = QRound(x [, n])

f: floating point variable
x: any numeric variable
n: integer

Description

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).

Example

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

Remarks

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

See Also

Ceil(), CInt, Frac(), Fix(), Floor(), Int(), Trunc()

{Created by Sjouke Hamstra; Last updated: 04/07/2022 by James Gaite}