xsharp.eu • X# round() behaviour
Page 1 of 3

X# round() behaviour

Posted: Fri Apr 19, 2019 8:13 pm
by Karl-Heinz
Folks,

try this with X# and VO

Code: Select all

? Round (  65.475 , 2 )  // must be 65.48 instead of 65.47
? Round ( -8.075 , 2 )   // must be -8.08 instead of -8.07
? Round ( 8.075 , 2 )   // must be 8.08 instead of 8.07
? Round ( 294.405 , 2 )   // must be 294.41 instead of 294.40 

i reanimated my FP 2.0 and it shows the same correct values as VO. Because VN also uses Math.round() VN shows the same wrong results as X#.

@Matt
can you please verify the reults with VFP ?

I have an idea how it might be fixed, but at first i would like to know your opinions.

BTW. I´m talking about the type float, so please no discussion about the type decimal :-)

regards
Karl-Heinz

X# round() behaviour

Posted: Fri Apr 19, 2019 9:17 pm
by FFF
Karl-Heinz,
indeed.
But try
? Round(3.475,2) // shows here 3.48 !
? Round(4.475,2) // shows here 4.47 !


Karl

X# round() behaviour

Posted: Fri Apr 19, 2019 10:00 pm
by Jamal
Karl,

Yes, it seems a bug. But, before I give you the (long) reason and sample test code, how did you fixed it.
And I am sorry, but It has to do with Decimal and Double.
BTW, this is a discussion forum!!

X# round() behaviour

Posted: Sat Apr 20, 2019 12:00 am
by Chris
Guys,

I suspect this is again a problem of floating point accuracy, the value 4.475 is probably being represented in binary as something like 4.4749999999989, so in this sense it is correct that Math.Round() rounds it down to 4.47 instead of 4.48.

Now the question is how to solve this...Maybe it is an idea to make Round() first call Math.Round(value,3), before doing a Math.Round(value,2), I think this should solve this.

X# round() behaviour

Posted: Sat Apr 20, 2019 12:06 am
by Chris
Hmm, another point is banker's rounding that Math.Round() uses by default, but in my tests I see that Math.Round() does not behave consistently in that way either. But it seems to be a precision problem indeed, reading from https://docs.microsoft.com/en-us/dotnet ... mework-4.8 :

Rounding and precision

In order to determine whether a rounding operation involves a midpoint value, the Round method multiplies the original value to be rounded by 10n, where n is the desired number of fractional digits in the return value, and then determines whether the remaining fractional portion of the value is greater than or equal to .5. This is a slight variation on a test for equality, and as discussed in the "Testing for Equality" section of the Double reference topic, tests for equality with floating-point values are problematic because of the floating-point format's issues with binary representation and precision. This means that any fractional portion of a number that is slightly less than .5 (because of a loss of precision) will not be rounded upward.

X# round() behaviour

Posted: Sat Apr 20, 2019 12:19 am
by Jamal
Chris,

I think it has to do with Decimal and Double because that's what the .NET Math.Round() expects. However, my testing indicates that when the value is passed as a number, it is of type REAL8 not FLOAT then the Math.Round() gets confused and even further the Round() function is NOT declaring the variables types correctly.

X# round() behaviour

Posted: Sat Apr 20, 2019 12:42 am
by Jamal
Here is a fix:

Code: Select all

FUNCTION RoundTest(n AS usual,iDec AS INT) AS USUAL
   LOCAL r     AS decimal
   LOCAL x as decimal
  
   x := FLOAT(n)   // must do this
       
   r := Math.Round( x, iDec, MidpointRounding.AwayFromZero ) 
  
return r
? RoundTest(65.475, 2 ) // returns 65.48

X# round() behaviour

Posted: Sat Apr 20, 2019 1:57 am
by FoxProMatt
FoxPro 9 SP2 gave these results:
( in bold):


? Round ( 65.475 , 2 ) // must be 65.48 instead of 65.47
? Round ( -8.075 , 2 ) // must be -8.08 instead of -8.07
? Round ( 8.075 , 2 ) // must be 8.08 instead of 8.07
? Round ( 294.405 , 2 ) // must be 294.41 instead of 294.40

X# round() behaviour

Posted: Sat Apr 20, 2019 10:06 am
by Karl-Heinz
Hi Jamal,

your RoundTest() crashes if iDec is negative, while a X# round like Round ( 9367, -2 ) shows correctly 9400.
Here´s the link to the Round() source code:

https://github.com/X-Sharp/XSharpPublic ... h.prg#L246

I think the problem is the part where iDec > 0 is handled.

Code: Select all

...

  IF iDec > 0

        // Round after decimal point
        IF iDec > MAX_DECIMALS
            iDec := MAX_DECIMALS
        ENDIF
        
        r8 := Math.Round( r8, iDec, MidpointRounding.AwayFromZero ) 

  ELSE 

...
In the earlier days VO had round problems too. I can't remember who posted a fix back then, but i modified it a little bit, so it looks now:

!! This is a quick shot only !!

Code: Select all

FUNCTION Round2 ( fVal AS USUAL , iDec AS INT ) AS USUAL PASCAL
    
    IF iDec > 0 

	IF IsFloat ( fVal )  .or. isdecimal ( fVal ) 
		 	
		IF fVal < 0
			   
			fVal := fVal - (  1 / 10 ^ ( iDec + 1 )  )  
				
		   		
		ELSE 
			
			fVal := fVal + (  1 / 10 ^ ( iDec + 1 )  )      		        	
		
	    	ENDIF  
	   	
			 
	ENDIF	    
		
    ENDIF
    
    RETURN XSharp.RT.Functions.Round (fVal , iDec )



Here are some Round() and Round2() results

Code: Select all

? "Round()"
? "-------"
?
? Round ( 65.475 , 2 )  // must be 65.48 instead of 65.47
? Round ( -8.075 , 2 )   // must be -8.08 instead of -8.07
? Round ( 8.075 , 2 )   // must be 8.08 instead of 8.07
? Round ( 294.405 , 2 )   // must be 294.41 instead of 294.40 
? Round (4.475,2)   	// must be 4.48 instead of 4.47 
? Round (  65.475 , -2 )  			// 100 
? Round ( 9367, -2 )   				// 9400
? Round (10.4, 0)                   // 10
? Round (10.5, 0)                   // 11
? Round (10.51, 0)                  // 11
? Round (10.49999999999999, 2)      // 10.50
? Round (101.99, -1)               	// 100
? Round (109.99, -1)               	// 110
? Round (109.99, -2)               	// 100
? Round2 (-101.99, -1)               // -100

? "Round2()"
? "-------"
?
? Round2 (  65.475 , 2 )  //  65.48
? Round2 ( -8.075 , 2 )   //   -8.08 
? Round2 ( 8.075 , 2 )   //  8.08
? Round2 ( 294.405 , 2 )   // 294.41 
? Round2 (4.475,2)   	//  4.48 
? Round2 (  65.475 , -2 )  			// 100 
? Round2  ( 9367, -2 )   			// 9400
? Round2 (10.4, 0)                  // 10
? Round2 (10.5, 0)                  // 11
? Round2 (10.51, 0)                 // 11
? Round2 (10.49999999999999, 2)     // 10.50
? Round2 (101.99, -1)               // 100
? Round2 (109.99, -1)               // 110
? Round2 (109.99, -2)               // 100
? Round2 (-101.99, -1)               // -100
regards
Karl-Heinz

X# round() behaviour

Posted: Sat Apr 20, 2019 10:25 am
by FFF
Jamal,
point is, we need not a quick fix, but consistency. It's unacceptable, when there's no predictability what a round returns ;)