I had written this function not long ago and was checking the results when i came acros this question, here you go:
Usage:
Round (x, 0.5, 1) ! Rounds up to halves
Round (x, 1.0, -1) ! Rounds down to wholes
Round (x, 0.01, 0) ! Standard rounding to cents
RoundAny PROCEDURE (REAL pExpression,REAL pOrder,REAL pDirection)
! ELA: Extension to standard round to support rounding to any number of digits with any value
CODE
IF pDirection < 0 THEN ! Round Down
RETURN INT(pExpression / pOrder) * pOrder
ELSIF pDirection > 0 THEN ! Round Up
RETURN INT((pExpression + pOrder - 1.0e-100) / pOrder) * pOrder
ELSE ! Round up/down
RETURN ROUND(pExpression / pOrder, 1) * pOrder
END
RoundAny function does not return value for CEILING(7.0). As Carl had pointed earlier, Ceiling(7.0) returns 7 as “equal to the specified decimal number”.
The following matches all of the expected values from Microsoft’s documentation
note: I normally use OutputDebugString instead of MESSAGE
but I wanted to make this extra easy for everyone to run for themselves
PROGRAM
MAP
Ceiling(REAL value),LONG
Floor(REAL value),LONG
Test(REAL value, LONG expectedCeiling, LONG expectedFloor),BOOL
END
Pass LONG(0)
Fail LONG(0)
CODE
IF Test( 7.03, 8, 7) THEN Pass += 1 ELSE Fail += 1 END
IF Test( 7.64, 8, 7) THEN Pass += 1 ELSE Fail += 1 END
IF Test( 0.12, 1, 0) THEN Pass += 1 ELSE Fail += 1 END
IF Test(-0.12, 0, -1) THEN Pass += 1 ELSE Fail += 1 END
IF Test(-7.1 , -7, -8) THEN Pass += 1 ELSE Fail += 1 END
IF Test(-7.6 , -7, -8) THEN Pass += 1 ELSE Fail += 1 END
IF Test(-7.0 , -7, -7) THEN Pass += 1 ELSE Fail += 1 END
IF Test( 7.0 , 7, 7) THEN Pass += 1 ELSE Fail += 1 END
MESSAGE('Passed['& Pass &']|Failed['& Fail &']','Tests complete')
Ceiling PROCEDURE(REAL value)
Answer LONG,AUTO
CODE
IF Value < 0 OR INT(Value) = Value
Answer = Value ! <-- rely on automatic type conversion to do the INT() for us for < 0
ELSE Answer = INT(Value) + 1
END
RETURN Answer
Floor PROCEDURE(REAL value)
! revised implementation by @RexCalifornia
CODE
IF INT(Value) = Value THEN RETURN Value
ELSIF Value < 0 THEN RETURN Value - 1 ! really INT(Value) - 1
ELSE RETURN Value ! really INT(Value)
END
! relying on automatic data type conversion to do the INT() for us
Test PROCEDURE(REAL value, LONG expectedCeiling, LONG expectedFloor)
Passed BOOL(TRUE)
CODE
IF Ceiling(Value) <> expectedCeiling |
OR Floor(Value) <> expectedFloor |
THEN
MESSAGE( 'Value['& Value &']' |
& '|Ceiling: Actual['& Ceiling(Value) &'] Expected['& expectedCeiling &']' |
& '|Floor : Actual['& Floor(Value) &'] Expected['& expectedFloor &']' |
, 'Test Failed'|
)
Passed = FALSE
END
RETURN Passed
Hi i think that is he used a very small number, which cannot be represented with Real variable limitations, after subtracted from the previous expression. Particulary as the mantisa is only 15 digits. It should work with for example 1.0e-5 for rounding numbers below 1.0e+10 but those ranges could not cover someone needs. The if solution would avoid all this, although I think comparing Reals with an specific value could be dangerous, I tend to use less than for that. eg intead if X = 0 i use IF ABS(x) < smallNumber being smallNumber less than the decimal presition used in x on the specific context.
Nice catch, that makes perfect sense.
I have revised the code in post marked as “Solution”
This also shows a problem of just writing to pass all of the test data
namely inputs and expected values that don’t cover all of the cases.
I’ve also added new test values for +7.0 and -7.0
REALs sometimes could behave differently as expected, for example look at this problem:
Traslated to Mark solution:
x real
CODE
IF Test( 7.03, 8, 7) THEN Pass += 1 ELSE Fail += 1 END
IF Test( 7.64, 8, 7) THEN Pass += 1 ELSE Fail += 1 END
IF Test( 0.12, 1, 0) THEN Pass += 1 ELSE Fail += 1 END
IF Test(-0.12, 0, -1) THEN Pass += 1 ELSE Fail += 1 END
IF Test(-7.1 , -7, -8) THEN Pass += 1 ELSE Fail += 1 END
IF Test(-7.6 , -7, -8) THEN Pass += 1 ELSE Fail += 1 END
IF Test(-7.0 , -7, -7) THEN Pass += 1 ELSE Fail += 1 END
IF Test( 7.0 , 7, 7) THEN Pass += 1 ELSE Fail += 1 END
IF Test( (.5*3+.5*1.6)*100 , 230, 230) THEN Pass += 1 ELSE Fail += 1 END !passes
x = .5*3+.5*1.6
IF Test( x*100 , 230, 230) THEN Pass += 1 ELSE Fail += 1 END !fails with floor=229
MESSAGE('Passed['& Pass &']|Failed['& Fail &']','Tests complete') !passed 9 failed 1
I agree, I would avoid passing DECIMAL to REAL if possible by overloading. Clarion’s BCD library was written to avoid issues with REAL’s a Decimal numbers. Something like 10.0 Decimal being 9.99999999 as a Real.
Could also prototype as a STRING and then convert to DECIMAL. Not the fastest but Clarion’s auto conversion works and (I think) no chance of a REAL oddity:
Floor(STRING valueStr),LONG
value DECIMAL(31,9)
CODE
value=ValueStr
...
Code previously posted I thought could be simpler by setting to an INT() first then using that for comparisons. This has NOT been tested:
Ceiling PROCEDURE(REAL value) !,LONG
Answer LONG,AUTO
CODE
Answer = INT(Value)
IF Answer <> Value AND Value > 0 THEN !Was NOT an Integer and Positve
Answer += 1 !So round up to higher Integer
END
RETURN Answer
Floor PROCEDURE(REAL value) !,LONG
Answer LONG,AUTO
CODE
Answer = INT(Value)
IF Answer <> Value AND Value < 0 THEN !Was NOT an Integer and Negative
Answer -= 1 !So round down to lower Integer
END
RETURN Answer
Returning a LONG does limit these to 2.147 billion which could be an issue. Probably should have Answer as REAL, or as a DECIMAL(31) and return a STRING.