X# round() behaviour

This forum is meant for questions and discussions about the X# language and tools
Karl-Heinz
Posts: 774
Joined: Wed May 17, 2017 8:50 am
Location: Germany

X# round() behaviour

Post 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
FFF
Posts: 1581
Joined: Fri Sep 25, 2015 4:52 pm
Location: Germany

X# round() behaviour

Post by FFF »

Karl-Heinz,
indeed.
But try
? Round(3.475,2) // shows here 3.48 !
? Round(4.475,2) // shows here 4.47 !


Karl
Regards
Karl
(on Win8.1/64, Xide32 2.20, X#2.20.0.3)
Jamal
Posts: 320
Joined: Mon Jul 03, 2017 7:02 pm

X# round() behaviour

Post 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!!
User avatar
Chris
Posts: 4922
Joined: Thu Oct 08, 2015 7:48 am
Location: Greece

X# round() behaviour

Post 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.
Chris Pyrgas

XSharp Development Team
chris(at)xsharp.eu
User avatar
Chris
Posts: 4922
Joined: Thu Oct 08, 2015 7:48 am
Location: Greece

X# round() behaviour

Post 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.
Chris Pyrgas

XSharp Development Team
chris(at)xsharp.eu
Jamal
Posts: 320
Joined: Mon Jul 03, 2017 7:02 pm

X# round() behaviour

Post 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.
Jamal
Posts: 320
Joined: Mon Jul 03, 2017 7:02 pm

X# round() behaviour

Post 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
FoxProMatt

X# round() behaviour

Post 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
Karl-Heinz
Posts: 774
Joined: Wed May 17, 2017 8:50 am
Location: Germany

X# round() behaviour

Post 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
FFF
Posts: 1581
Joined: Fri Sep 25, 2015 4:52 pm
Location: Germany

X# round() behaviour

Post 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 ;)
Regards
Karl
(on Win8.1/64, Xide32 2.20, X#2.20.0.3)
Post Reply