Page 1 of 2

Strange behavior of Abs()! @PureBasicTeam

Posted: Tue Jan 02, 2024 10:24 pm
by SMaag
we found out a strange behavior of the Abs() function.

It is a Float function, but can we use it for big Quads too? More than 53Bits!

Is it internal overladed with a AbsQ() function which is not documented?

The problem you can see at Q= -100000000000000005

Abs(-100000000000000005) -> 100000000000000000.0
But if we use with a quad variable the result is 100000000000000005
what is correct for a quad!

Code: Select all

EnableExplicit

Macro mac_AbsInt(val)
  If val < 0 : val = -val : EndIf  
EndMacro

Define.q Q, r, s

Q= -100000000000000005


r = Abs(Q)
s = Q
mac_AbsInt(s)

Debug r
Debug s
Debug Abs(Q)

Re: Strange behavior of Abs()! @PureBasicTeam

Posted: Wed Jan 03, 2024 12:51 am
by jacdelad
I may be wrong, but since a.q=Abs(b.q) works, maybe it's a problem with Debug, not Abs()???

Re: Strange behavior of Abs()! @PureBasicTeam

Posted: Wed Jan 03, 2024 12:51 am
by mk-soft
The ABS function is a float / double function. This means that it cannot be accurate for quad, as the quad is first converted into a double and then back into a quad.

Code: Select all

// r = Abs(Q)
double r0=PB_Abs(v_q);
v_r=SYS_BankerRoundQuad(r0);

Re: Strange behavior of Abs()! @PureBasicTeam

Posted: Wed Jan 03, 2024 11:49 am
by STARGÅTE
I fully agree with mk-soft, Abs() is a floating point function. However, I wonder, why it works for quads as well (without quad->double->quad conversion) in the ASM backend with explicit type definition:

Code: Select all

Define Quad.q = -$7FFFFFFFFFFFDEAD
Quad = Abs(Quad)
Debug Hex(Quad)
ASM backend wrote:7FFFFFFFFFFFDEAD
C backend wrote:7FFFFFFFFFFFE000
So there must be a hidden function overloading in case of the ASM backend.

Re: Strange behavior of Abs()! @PureBasicTeam

Posted: Wed Jan 03, 2024 9:10 pm
by SMaag
Maybe the following explains the correct working of ABS() for Quads

if we look at the assembly output

Code: Select all

x.i = -100000000000000005
y.i = Abs(x)

; y.i = Abs(x)
  FILD   dword [v_x]
  FABS
  FISTP  dword [v_y]

here the documentation of the ASM commands

FILD
Converts the signed-integer source operand into double extended-precision floating-point format and pushes the value onto the FPU register stack. The source operand can be a word, doubleword, or quadword integer. It is loaded without rounding errors. The sign of the source operand is preserved.

In the Intel Manual I found:
The x87 FPU recognizes and operates on the following seven data types (see Figures 8-13): single precision
floating-point, double precision floating-point, double extended precision floating-point, signed word integer,
signed doubleword integer, signed quadword integer, and packed BCD decimal integers.
(https://cdrdv2-public.intel.com/671436/ ... -vol-1.pdf
page 215)


My conclusion is: it is a debug problem!
because x87 Floating Point are probably 80Bit (64Bit Fraction, 15Bit Exponent an 1 Sign, so the FPU can store the complete 64Bit of a Quad without rounding)

For x64 Processors we can use ABS() for Quads without a rounding error!

Re: Strange behavior of Abs()! @PureBasicTeam

Posted: Wed Jan 03, 2024 10:59 pm
by mk-soft
Not good,

not on C-Backend !

Re: Strange behavior of Abs()! @PureBasicTeam

Posted: Thu Jan 04, 2024 7:40 am
by juergenkulow

Code: Select all

; Abs in C backend changes values
q.q=-Random(100000000000000006,100000000000000005)
absq.q=Abs(q)
Debug q
Debug absq
; ASM Backend
; -100000000000000006
; 100000000000000006
; C Backend 
; -100000000000000006
; 100000000000000000
;                  ^

Re: Strange behavior of Abs()! @PureBasicTeam

Posted: Thu Jan 04, 2024 10:16 am
by wilbert
With the c backend you could also use llabs

Code: Select all

int64.q = -100000000000000005
!v_int64 = llabs(v_int64);
Debug int64

Re: Strange behavior of Abs()! @PureBasicTeam

Posted: Thu Jan 04, 2024 11:02 am
by SMaag
Oh shit!
I feel like I opepend "Pandora's Box"!

It would be time for a PB integrated AbsInt() function

Re: Strange behavior of Abs()! @PureBasicTeam

Posted: Thu Jan 04, 2024 11:20 am
by Kiffi
SMaag wrote: Thu Jan 04, 2024 11:02 amIt would be time for a PB integrated AbsInt() function
Skål (scnr)

Re: Strange behavior of Abs()! @PureBasicTeam

Posted: Thu Jan 04, 2024 1:19 pm
by SMaag
Really a difference in ASM and C-Backend

Code: Select all

Macro mac_AbsQ1(number)
  (number ! (number >> 63) + (number >> 63) &1)
EndMacro

Macro mac_AbsQ2(number)
    ((number + (number >> 63)) ! (number >> 63))  
EndMacro

Procedure AbsQ(number)
  If number < 0
    ProcedureReturn -number
  EndIf
  ProcedureReturn number  
EndProcedure


Define I, K, N, t1

t1 = ElapsedMilliseconds()

#Start = 100000000000000005

For I = #Start To #Start +32
  Debug " "
  K = -I
  N = mac_AbsQ1(K)
  Debug "AbsQ1 = " + N
  
  N = mac_AbsQ2(K)
  Debug "AbsQ2 = " + N
  
  N = AbsQ(K)
  Debug "AbsQ = " + N
  
  ; Abs() is different in ASM and C-Backend
  N = Abs(K)
  Debug "Abs() = " + N

Next

t1 = ElapsedMilliseconds() - t1

MessageRequester("Time", Str(t1))

here the C Version Output => not correct! With ASM it's correct!

AbsQ1 = 100000000000000005
AbsQ2 = 100000000000000005
AbsQ = 100000000000000005
Abs() = 100000000000000000

AbsQ1 = 100000000000000006
AbsQ2 = 100000000000000006
AbsQ = 100000000000000006
Abs() = 100000000000000000

AbsQ1 = 100000000000000007
AbsQ2 = 100000000000000007
AbsQ = 100000000000000007
Abs() = 100000000000000000

AbsQ1 = 100000000000000008
AbsQ2 = 100000000000000008
AbsQ = 100000000000000008
Abs() = 100000000000000000

AbsQ1 = 100000000000000009
AbsQ2 = 100000000000000009
AbsQ = 100000000000000009
Abs() = 100000000000000016

AbsQ1 = 100000000000000010
AbsQ2 = 100000000000000010
AbsQ = 100000000000000010
Abs() = 100000000000000016

AbsQ1 = 100000000000000011
AbsQ2 = 100000000000000011
AbsQ = 100000000000000011
Abs() = 100000000000000016

AbsQ1 = 100000000000000012
AbsQ2 = 100000000000000012
AbsQ = 100000000000000012
Abs() = 100000000000000016

AbsQ1 = 100000000000000013
AbsQ2 = 100000000000000013
AbsQ = 100000000000000013
Abs() = 100000000000000016

AbsQ1 = 100000000000000014
AbsQ2 = 100000000000000014
AbsQ = 100000000000000014
Abs() = 100000000000000016

AbsQ1 = 100000000000000015
AbsQ2 = 100000000000000015
AbsQ = 100000000000000015
Abs() = 100000000000000016

AbsQ1 = 100000000000000016
AbsQ2 = 100000000000000016
AbsQ = 100000000000000016
Abs() = 100000000000000016

AbsQ1 = 100000000000000017
AbsQ2 = 100000000000000017
AbsQ = 100000000000000017
Abs() = 100000000000000016

Re: Strange behavior of Abs()! @PureBasicTeam

Posted: Thu Jan 04, 2024 1:50 pm
by STARGÅTE
SMaag wrote: Thu Jan 04, 2024 1:19 pm Really a difference in ASM and C-Backend
[...]
here the C Version Output => not correct! With ASM it's correct!
Sure, from the mathematical point of view the ASM backend is correct.
However, I would say, it is a bug in the ASM backend, not in the C backend.
In the documentation it is clearly written, Result.d = Abs(Number.d).
This means, also the ASM backend has to perform a type cast from Quad to Double to Quad.

There was a similar bug in older PB versions with trigonometric functions.
This code has given "Unsame" because y was calculated with double precision (64 bit), in the comparison, however, Cos(x) as well as the test on equality was performed with FPU 80 bit precision.

Code: Select all

Define x.d = 0.2
Define y.d = Cos(x)

If y = Cos(x)
	Debug "Same"
Else
	Debug "Unsame"
EndIf
This was fixed now in PB 6.0.

Re: Strange behavior of Abs()! @PureBasicTeam

Posted: Thu Jan 04, 2024 5:02 pm
by SMaag
Sure, from the mathematical point of view the ASM backend is correct.
However, I would say, it is a bug in the ASM backend, not in the C backend.
In the documentation it is clearly written, Result.d = Abs(Number.d).
This means, also the ASM backend has to perform a type cast from Quad to Double to Quad.
I don't think it is a bug! Not in the ASM and not in the C-Backend. It's a different handling because C
has it's own optimation routines!

in the ASM Backend
the FILD command is used to load the integer value to the floating point registers.
The Intel documentation says
The source operand can be a word, doubleword, or quadword integer. It is loaded without rounding errors. The sign of the source operand is preserved.
So when using the FILD command there is no rounding!

C use an other methode for the Abs()
I did an IDA disassemble of the code

Code: Select all

s.s ="123"
x.q = 7
y.q = Abs(x)

MessageRequester("Result", "Abs() = " + Y)

; here the disassembley from C-Backend

mov     cs:qword_140005730, 7
movsd   xmm0, cs:qword_140003140
call    sub_140001A90

sub_140001A90 proc near
andps   xmm0, cs:xmmword_140003160
retn
sub_140001A90 endp

xmmword_140003160 xmmword 7FFFFFFFFFFFFFFF7FFFFFFFFFFFFFFFh
C use ANDPS Bitwise Logical AND of Packed Single Precision Floating-Point Values

so the Abs() is
ANDPS number, 7FFFFFFFFFFFFFFF7FFFFFFFFFFFFFFFh

This don't work for Integers, doesnt matter 32 or 64 Bit

I do not understand what should be the adbantage of this
For me it seems slower than FILD

Re: Strange behavior of Abs()! @PureBasicTeam

Posted: Thu Jan 04, 2024 5:14 pm
by SMaag
if we use in PB the ABS() with an Integer Value, this is the disassembley

Code: Select all

call    sub_140001B30
mov     cs:qword_140005730, 7
movsd   xmm0, cs:qword_140003140
call    sub_140001A90  ; what is a simple andps   xmm0, 7FFFFFFFFFFFFFFF7FFFFFFFFFFFFFFFh
movsd   [rsp+38h+var_18], xmm0

; after removing the sing bit
; the value is pused on the float stack
fld     [rsp+38h+var_18]	  ; but this is not a float it is an integer			
fistp   [rsp+38h+var_10	; here we convert an Integer back to int! That can't be	
mov     rax, [rsp+38h+var_10]
if we use in PB the Abs() with a double value, This is the disassembley

Code: Select all

call    sub_140001CC0
movsd   xmm0, cs:qword_140003160
movsd   cs:qword_140005770, xmm0
call    sub_140001C20
movsd   cs:qword_140005768, xmm0 ; with doubles there is no converting back to Integer
for the ASM Backend we get this

Code: Select all


with the ASM Backend Code
  FILD   dword [v_x]	; here we load an integer to the FloatingPoint register and convert it to Float
  FABS
  FISTP  dword [v_y]	; convert back the Float to integer

Conclusion: PB handle the ABS() in the C-Backend different for Floating Point and Integers.
with intgers there is an additional command to convert back from float to int with this 2 commands

fld [rsp+38h+var_18] ; but this is not a float it is an integer ; !I was wrong, it is a float!
fistp [rsp+38h+var_10 ; here we convert an Integer back to int! That can't be !it's correct too!

but this is to much. It is still an integer and a conversion is not necessary!

Thats a BUG of the PB Compiler!
No it is not a Bug, see 2 posts later! It's a tricky 64-Bit float handling!

Re: Strange behavior of Abs()! @PureBasicTeam

Posted: Thu Jan 04, 2024 7:07 pm
by Psychophanta
My pi/2 cents of workarounded pragmatic solution for this issue:

Code: Select all

Macro absforints(v)
  Val(LTrim(Str(v#),"-"))
EndMacro
; OR if you prefer:
Procedure.q absforintsASM(v.q)
  !fild qword[p.v_v]
  !fabs
  !fistp qword[p.v_v]
  ProcedureReturn v
EndProcedure

debug absforints(-100000000000000005); :D
debug absforints(100000000000000005); :D

debug absforintsASM(-100000000000000005); :D
debug absforintsASM(100000000000000005); :D