Page 1 of 2

Round(0.51, #PB_Round_Nearest) <> {debug integer 0.51}

Posted: Sun May 18, 2025 8:54 pm
by Olli

Code: Select all

x.d = 0 50
y.d = 0.51

a.i = Round(x, #PB_Round_Nearest)
b.i = x

Debug a
Debug b
Why ?

Re: Round(0.51, #PB_Round_Nearest) <> {debug integer 0.51}

Posted: Sun May 18, 2025 8:59 pm
by Michael Vogel
As defined.
x.i=f.d equals x.i=Int(f.d)

Re: Round(0.51, #PB_Round_Nearest) <> {debug integer 0.51}

Posted: Sun May 18, 2025 9:21 pm
by AZJIO
1<>0
All right.

Re: Round(0.51, #PB_Round_Nearest) <> {debug integer 0.51}

Posted: Sun May 18, 2025 9:27 pm
by Olli
Thank you my friend. So why ?

Re: Round(0.51, #PB_Round_Nearest) <> {debug integer 0.51}

Posted: Sun May 18, 2025 10:09 pm
by Demivec
Olli wrote: Sun May 18, 2025 9:27 pm Thank you my friend. So why ?
Michael Vogel wrote: Sun May 18, 2025 8:59 pm As defined.
x.i=f.d equals x.i=Int(f.d)

Re: Round(0.51, #PB_Round_Nearest) <> {debug integer 0.51}

Posted: Sun May 18, 2025 10:21 pm
by AZJIO
Round(x, #PB_Round_Nearest) = 1.0
The variable x.i transforms 1.0 into 1
integer or int throws out the fractional part. If you remove the fractional part from the number 0.5, then 0 remains.

Re: Round(0.51, #PB_Round_Nearest) <> {debug integer 0.51}

Posted: Sun May 18, 2025 10:44 pm
by Olli
This...

Code: Select all

Macro test(xxx)

x.d = xxx
a.i = Round(x, #PB_Round_Nearest)
b.i = x

Debug a
Debug b
EndMacro

test(0.50)
test(0.51)
...gives that...

Code: Select all

1
0
1
1
My question is "why ?"

Why the non-explicit double-to-integer converter has a light different behaviour, compared to the Round() native function ?

Where is the bug, by the way ? My suggest tends to the non-explicit converter...

Re: Round(0.51, #PB_Round_Nearest) <> {debug integer 0.51}

Posted: Sun May 18, 2025 11:42 pm
by TassyJim
Olli wrote: Sun May 18, 2025 10:44 pm Why the non-explicit double-to-integer converter has a light different behaviour, compared to the Round() native function ?
It is the same as Int(), not Round()
This function returns an integer value. To get a quad value, use the IntQ() function.

The integer part is created by cutting off the fractional part of the value. There is no rounding. To perform rounding, use the Round() function.

Re: Round(0.51, #PB_Round_Nearest) <> {debug integer 0.51}

Posted: Mon May 19, 2025 12:38 am
by AZJIO
TassyJim wrote: Sun May 18, 2025 11:42 pm It is the same as Int(), not Round()

Code: Select all

b.i = x
the conversion is not Int() or Round()
The Int() function gives 0 in both cases.
The Round() function gives 1 in both cases.

Code: Select all

; Step 1
x.d = 0.51
b.i = x
Debug Int(x)
Debug Round(x, #PB_Round_Nearest)
Debug b

; Step 2
x.d = 0.50
b.i = x
Debug Int(x)
Debug Round(x, #PB_Round_Nearest)
Debug b
Apparently b.i = x has its own simplified rounding method.

Re: Round(0.51, #PB_Round_Nearest) <> {debug integer 0.51}

Posted: Mon May 19, 2025 8:08 am
by Demivec
@Michael Vogel: Apparently the default conversion method from float to integer is not truncation but "banker's rounding". I had assumed this as being my past experience but I was in error. I didn't conduct a test in past PureBasic versions but historical forum posts seem to indicate this was present in past versions though in versions prior to v6.0 there was a bug that created inconsistencies between doing 'b.i = x.d' and 'b.i = 1.5 + 3.0' because of the literals were being combined during compilations using the rounding method 'Nearest' instead of the usual 'banker's rounding' that was used for type conversion other values (from variables or expressions) to integer.

Here's a comparison of PureBasic's rounding methods:

Code: Select all

;Test code to show PureBasic's included rounding methods.
;  Other methods are available through custom routines.
;
;PureBasic version 6.20
;Date of last change: 5/18/2025

EnableExplicit

Macro mcr_center(_string_, _w_, _padChar_=" ")
  ReplaceString(Space(_w_ - (Len(_string_) + (_w_ - Len(_string_)) / 2)), " ", _padChar_) +
_string_ +
ReplaceString(Space((_w_ - Len(_string_)) / 2), " ", _padChar_)
EndMacro

Macro mcr_padRSet(_string_, _w_, _p_, _padChar_=" ")
  ReplaceString(Space(_p_), " ", _padChar_) + RSet(_string_, _w_, _padChar_)
EndMacro

Define x.d, b.i, o$, i

Debug "Rounding Methods" + #LF$ + RSet("", 16, "=")    
o$ = mcr_center("", 12) +
     mcr_center("Type d->i", 12) +
     mcr_center("Int()", 12) +
     mcr_center("Up", 12) +
     mcr_center("Down", 12) +
     mcr_center("Nearest", 12)    
Debug o$
o$ = mcr_center("x.d", 12) +
     mcr_center("(banker's)", 12) +
     mcr_center("(truncate)", 12) +
     mcr_center("(ceiling)", 12) +
     mcr_center("(floor)", 12) +
     mcr_center("(half up)", 12)    ;away from zero, to be more negative or more positive
Debug o$
o$ = ReplaceString("xxxxxx", "x", ReplaceString(" - ", "-", RSet("", 10, "-"))) 
Debug o$

x.d = -5.5
For i = 1 To 21
  x.d  +  0.5
  b.i  =  x.d ;invokes the default rounding from float to integer used by IE 754 ('banker's rounding')
  
  o$ = mcr_padRSet(StrD(x, 4), 8, 2) + 
       mcr_padRSet("" + Str(b), 10, 2) +
       mcr_padRSet("" + Int(x), 10, 2) +
       mcr_padRSet("" + Round(x, #PB_Round_Up), 10, 2) +
       mcr_padRSet("" + Round(x, #PB_Round_Down), 10, 2) +
       mcr_padRSet("" + Round(x, #PB_Round_Nearest), 10, 2)
  Debug o$
Next
Outputs:

Code: Select all

Rounding Methods
================
              Type d->i     Int()        Up         Down       Nearest  
     x.d     (banker's)  (truncate)   (ceiling)    (floor)    (half up) 
 ----------  ----------  ----------  ----------  ----------  ---------- 
   -5.0000          -5          -5          -5          -5          -5
   -4.5000          -4          -4          -4          -5          -5
   -4.0000          -4          -4          -4          -4          -4
   -3.5000          -4          -3          -3          -4          -4
   -3.0000          -3          -3          -3          -3          -3
   -2.5000          -2          -2          -2          -3          -3
   -2.0000          -2          -2          -2          -2          -2
   -1.5000          -2          -1          -1          -2          -2
   -1.0000          -1          -1          -1          -1          -1
   -0.5000           0           0           0          -1          -1
    0.0000           0           0           0           0           0
    0.5000           0           0           1           0           1
    1.0000           1           1           1           1           1
    1.5000           2           1           2           1           2
    2.0000           2           2           2           2           2
    2.5000           2           2           3           2           3
    3.0000           3           3           3           3           3
    3.5000           4           3           4           3           4
    4.0000           4           4           4           4           4
    4.5000           4           4           5           4           5
    5.0000           5           5           5           5           5
For those not familiar with it, Banker's Rounding will round fractions of one-half to the nearest EVEN integer. So this will transform each of the following list of values to the value following following the '->': -3.5 -> -4 , -2.5 -> -2, -1.5 -> -2, -0.5 -> 0, 0.5 -> 0, 1.5 -> 2, 2.5 -> 2, 3.5 -> 4.

Re: Round(0.51, #PB_Round_Nearest) <> {debug integer 0.51}

Posted: Mon May 19, 2025 9:18 am
by Michael Vogel
Hi, this happens when the RC field (Rounding Control) of the FPU is set to "00" which means "Round to nearest, or to even if equidistant". You can also set the flag to "01" (Round down), "10" (Round up) or the expected "11" (toward 0).

Would be nice to have an compiler option to modify the state :wink:

Code: Select all


FPU.w

!fstcw	word[v_FPU]
!mov	ax,	[v_FPU]
;!or 	[v_FPU],	0000010000000000b; down
;!or 	[v_FPU],	0000100000000000b; up
!or 	[v_FPU],	0000110000000000b; trunk
!fldcw	word[v_FPU]

For n=0 To 10
	f.f=n/10
	i.i=f
	b.i=f+0.5
	Debug StrF(f,3)+" -> "+StrF(i)+" -> "+StrF(b)
Next n

Re: Round(0.51, #PB_Round_Nearest) <> {debug integer 0.51}

Posted: Mon May 19, 2025 9:26 am
by Demivec
Michael Vogel wrote: Mon May 19, 2025 9:18 am Hi, this happens when the RC field (Rounding Control) of the FPU is set to "00" which means "Round to nearest, or to even if equidistant". You can also set the flag to "01" (Round down), "10" (Round up) or the expected "11" (toward 0).

Would be nice to have an compiler option to modify the state :wink:
Well, each of those settings is currently available through various means. There's actually 5 methods available. The one you didn't mention is "Round to nearest if equidistant". Three are obtainable with the Round() function, one with Int(). The last is obtainable in one of three ways: as a type conversion from float to integer when assigning a value to a integer variable; using the Val() of either StrD() or StrF() with a decimal value that ends in exactly '.5' and that uses 0 for the digits parameter.

Re: Round(0.51, #PB_Round_Nearest) <> {debug integer 0.51}

Posted: Sat May 24, 2025 10:04 pm
by Olli
AZJIO wrote:Apparently b.i = x has its own simplified rounding method.
Yes, it does. I thank you to this observation.

Thanks to Demivec and Michael Vogel for the work you have done.
Michael Vogel wrote:Would be nice to have an compiler option to modify the state :wink:
I will see if there is not already a feature request about rounding method. If no, modifying the title, I ll demand to move this subject in the 'feature request' section.

Mathematically, round 0.49 is 0 and round 0.5 is 1. But I did not know the floats hardware gave so many operating modes...

I think show to you, where does the root of this subject come from.

Re: Round(0.51, #PB_Round_Nearest) <> {debug integer 0.51}

Posted: Sat May 24, 2025 10:13 pm
by Demivec
Olli wrote: Mathematically, round 0.49 is 0 and round 0.5 is 1. But I did not know the floats hardware gave so many operating modes...
There are many operating modes because mathematically 0.5 is the same distance to both 1 and 0. That means the decision on what to do when rounding it is not clear cut. This opens things up to more than a few choices, each with their own pluses and minuses.

Re: Round(0.51, #PB_Round_Nearest) <> {debug integer 0.51}

Posted: Sun May 25, 2025 1:24 am
by Olli
@Demivec

You are right, for the geometry. But for the algebra, with 10 digits from 0 to 9, if we separate the 10 digits in two sets, a low set, and a high set, we get 2 groups :
low group = 0, 1, 2, 3 and 4
high group = 5, 6, 7, 8 and 9

5 is so considered in the high half way in the results set characterized by x being between 0 included and 10 excluded.
(
x belongs to [0 ; 10[
or also
0 =< x < 10
)

Adding that the sign swaps the borns : x belongs to [-10; 0[
-10 =< x < 0. This gives -5 rounded to 10 is zero (0, not -10).

So, you are right : I am wrong, excluding the geometry and the equal distances. But in an equation, it is ruled.
5 is 10 (+/- 10)
-5 is 0 (+/- 10)