Clarion Rounding Values (similar to: Math.Ceiling Method)

Hello -

What would be an ideal equivalent in Clarion to round value up/down similar to these .NET Math.Ceiling Method and Math.Floor Method

Example:

Math.Ceiling Method (System) | Microsoft Docs

decimal values = {7.03m, 7.64m, 0.12m, -0.12m, -7.1m, -7.6m};
Console.WriteLine(" Value Ceiling Floor\n");
foreach (decimal value in values)
Console.WriteLine("{0,7} {1,16} {2,14}",
value, Math.Ceiling(value), Math.Floor(value));
// The example displays the following output to the console:
// Value Ceiling Floor
//
// 7.03 8 7
// 7.64 8 7
// 0.12 1 0
// -0.12 0 -1
// -7.1 -7 -8
// -7.6 -7 -8

I think this works, but please test:

 ceiling = ROUND(value + 0.5,1)
 floor = ROUND(value - 0.5,1)
1 Like

Ceiling(Decimal)

Returns the smallest integral value that is greater than or equal to the specified decimal number.

I think Ceiling(7.0) returns 7 as “equal to the specified decimal number”

While Round(7+0.5,1) returns 8.

So maybe + .4999 or use an if like Choose(x=int(x), x, Int(x)+1) but double check how that will work with negatives.

4 Likes

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
1 Like

Thank you Elankreijer.

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

x REAL
x= 7.00
RoundAny (x, 1.0, 1)

Returns 8.00, correct value is 7.00

I think Carl’s suggestion of checking if x = int(x) before doing any rounding is a good idea.

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
1 Like

Thank you, Mark, this is very helpful!

Just noticed that Floor Procedure does not return correct value for -7.00, correct value is -7.00

MS Excel has similar function and that can be used for verifying.

Floor  PROCEDURE(REAL value)
  CODE
    IF INT(Value) = Value 
      RETURN Value
    ELSIF  Value < 0 
      RETURN Value - 1  
    ELSE
      RETURN Value 
    END

image

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

Hi

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

Yes small errors are often a problem when using REAL numbers.

I always suggest REALs should be avoided where you need an accurate answer.

1 Like

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.

Floor(CONST *DECIMAL value),LONG

Floor(REAL value),LONG

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.

1 Like