IfUnsigned a < b

Got an idea for enhancing PureBasic? New command(s) you'd like to see?
Olli
Addict
Addict
Posts: 1202
Joined: Wed May 27, 2020 12:26 pm

IfUnsigned a < b

Post by Olli »

Bit #63 (1st MSB) can bring some errors.

a = $F0 << 56
b = $07 << 56

Normal (signed) compare :
a < b

Unsigned compare :
a > b

So, a new IfUnsigned or UnsignedIf should be required to allow the coder to choose.

It is just a op change : JB instead of JL on ASM.
User avatar
idle
Always Here
Always Here
Posts: 5840
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: IfUnsigned a < b

Post by idle »

better yet just add unsigned types. It's not difficult!
Olli
Addict
Addict
Posts: 1202
Joined: Wed May 27, 2020 12:26 pm

Re: IfUnsigned a < b

Post by Olli »

Hello idle,

I have seen your message. And good link :
viewtopic.php?t=65312.

Note that this feature request here is just a small internal change. And this is not the add of a new type unsigned.

More detailed, this would modify the four condition ops :

If a < b
If a =< b
If a > b
If a >= b

And this could only concern the bold conditions above. So, really not a lot.
User avatar
jacdelad
Addict
Addict
Posts: 1993
Joined: Wed Feb 03, 2021 12:46 pm
Location: Riesa

Re: IfUnsigned a < b

Post by jacdelad »

Wouldn't it be better to use different operator than a new If? This IfUnsigned limits you to one value, a new operator enables multiple comparations at once. And signed an not signed mixed together.
Good morning, that's a nice tnetennba!

PureBasic 6.21/Windows 11 x64/Ryzen 7900X/32GB RAM/3TB SSD
Synology DS1821+/DX517, 130.9TB+50.8TB+2TB SSD
Olli
Addict
Addict
Posts: 1202
Joined: Wed May 27, 2020 12:26 pm

Re: IfUnsigned a < b

Post by Olli »

jacdelad wrote: Mon Oct 09, 2023 10:15 am Wouldn't it be better to use different operator than a new If? This IfUnsigned limits you to one value, a new operator enables multiple comparations at once. And signed an not signed mixed together.
So, yes and no.

Yes, it limits. And this pushes to add parenthesis.

Code: Select all

IfUnsigned(condition)
No, it is possible not to be limited. Idle (that I thank for this) shows, in his work, an asm macro change can be used and shut down.

So, it is limited in the program, but in these limits, any values, and any conditions can be tested, but ever as unsigned.

It is a few like floating operations between integers, or integer operations between floats : the mode is limited and determined by the type of destination on the statement start.

The main problem is, it seems more complex in C where a temporary destination should be required.
Quin
Addict
Addict
Posts: 1131
Joined: Thu Mar 31, 2022 7:03 pm
Location: Colorado, United States
Contact:

Re: IfUnsigned a < b

Post by Quin »

This could work, but I don't see why not just add unsigned types. This feels like a bandaid at best, and if they implement unsigned types (PLEASE DO!) they'll probably have to rip it out and deprecate old code.
User avatar
idle
Always Here
Always Here
Posts: 5840
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: IfUnsigned a < b

Post by idle »

Adding unsigned types isn't hard to do in the assembly and it's dead to do in c and it wouldn't break anything.

Fortunately It's easy enough to redefine a type as unsigned in the c backend for example

Code: Select all

Global x.l,y.l 

!unsigned int g_x;   
!unsigned int g_y;   

x = -1 
y =  1 

If x > y   ;if x is unsigend its 4294967295
  Debug x  
Else       ;if x is signed it's -1     
  Debug x 
EndIf   

User avatar
yuki
Enthusiast
Enthusiast
Posts: 101
Joined: Sat Mar 31, 2018 9:09 pm

Re: IfUnsigned a < b

Post by yuki »

As much as I'd love unsigned support, Fred and Timo seemed to pretty strongly reject it in the past:
https://www.purebasic.fr/english/viewtopic.php?p=295680#p295680 wrote: Fri Aug 14, 2009 3:06 pm These are primary for use to manipulate characters (ascii or unicode) indepently of the main 'Unicode' flag (unlike the .c type). Indeed, it works perfectly as well for unsigned byte and word. That said we don't plan to add further unsigned types.
https://www.purebasic.fr/english/viewtopic.php?p=296495#p296495 wrote: Tue Aug 18, 2009 11:03 pm We don't see much value in adding unsigned long or quad especially since all PB commands expect signed long or quad numbers as input. An unsigned byte or word can be cast to a signed long and passed to all these functions without problems, but passing an unsigned long to a function that expects a signed one is trouble. So what good is an unsigned type when you can pass it to almost no function to work with the value ?

Also as Fred explains in the link by luis, mixing signed and unsigned is very tricky in the implementation/performance department. For example to compare an unsigned byte to a signed long, you just cast the unsigned byte to a long and then compare (the same that needs to be done to compare signed byte and signed long). To compare an unsigned long with a signed one is a different story. The cases where the signed one is negative or the unsigned one is over the range of a signed long have to be handled separately because a direct comparison between both is not possible. Casting both up to quad just for a comparison is also not very fast on the x86 processor family as its over the native register size.

In the end, the number of situations where there is a real need for unsigned longs is quite low in my opinion (except to interface with external libraries maybe). So in our opinion it is just not worth the hassle.
From an implementation standpoint, it might not be too bothersome, though Timo's point about it being unintuitive is sound.

For example, given:

Code: Select all

Define x.ui = 1         ; .ui → unsigned int   (.iu or .i-unsigned might be alternatives?)
Define y.i  = -1

If x > y
    Debug "" + x + " is greater than " + y
Else
    Debug "" + x + " is not greater than " + y
EndIf
Which of the following is printed?
  • "1 is greater than -1"
  • "1 is not greater than -1"
  • nothing: it's a compiler error
(presumably, y stringifies to -1 and not 18446744073709551615 because it is signed)

In C, the comparison is effectively "If 1 > 18446744073709551615" and so following that logic we'd see "1 is not greater than -1" logged (which is a really silly erroneous statement). In this case, the programmer seemingly forgot a StrU(…).

If PB were to handle the comparison by first ensuring y is a non-negative signed value, this lowers performance, but has the more sensible "1 is greater than -1" logged. IMO, comparing the actual numeric values represented in this way is more natural than intricacies of bits behind-the-scenes. So arguably this might be the more BASIC route.

Alternatively, would the comparison just be forbidden entirely?

My dream would be to have unsigned added with safety checks to prevent ambiguous mixing and allow opt-in to either comparison form:

Code: Select all

; 1) Writing signed values to unsigned is an error.
; ═══════════════════════════════════════════════════════
Define x.ul = -1    ; ERR: Signed value `-1` cannot be assigned to unsigned long `x.ul`. Either assign to `x.ul\signed` to perform the necessary cast, or supply a value in range 0 .. 4_294_967_295.

Define y.l  = 1
Define x.ul = y     ; ERR: Signed `y.l` cannot be assigned to unsigned long `x.ul`. Assign to `x.ul\signed` to perform the necessary cast.

; 2) Writing unsigned values to signed is an error.
; ═══════════════════════════════════════════════════════
Define y.l  = 4294967295    ; OK: backwards-compatibility.

Define x.ul = 1
Define y.l  = x     ; ERR: Unsigned `x.ul` cannot be assigned to signed long `y.l`. Assign to `y.l\unsigned` to perform the necessary cast.

; 3) Comparisons are signedness-aware.
; ═══════════════════════════════════════════════════════
Define x.ul = 1
Define y.l  = -1

; ERR:  Comparing unsigned `x.ul` against signed `y.l` is ambiguous.
;       Specify the type of comparison with either `x\signed > y` or `x > y\unsigned`.
;       Alternatively, opt-in to mixed-signedness comparison by clarifying both sides, e.g.: `x\unsigned > y\signed`.
If x > y
    Debug "" + x + " is greater than " + y
Else
    Debug "" + x + " is not greater than " + y
EndIf

;  OK:  Comparison is treated as signed.
;       Becomes: If 1 > -1
;       Prints: "1 is greater than -1"
If x\signed > y
    Debug "" + x + " is greater than " + y
Else
    Debug "" + x + " is not greater than " + y
EndIf

;  OK:  Comparison is treated as unsigned.
;       Becomes: If 1 > 18446744073709551615
;       Prints: "1 is not greater than 18446744073709551615"
If x\unsigned > y
    Debug "" + x + " is greater than " + StrU(y)
Else
    Debug "" + x + " is not greater than " + StrU(y)
EndIf

;  OK:  Comparison is mixed-signedness-aware.
;       Becomes: If (-1 < 0) Or (1 > 18446744073709551615)
;       Prints: "1 is greater than -1"
If x\unsigned > y\signed
    Debug "" + x + " is greater than " + y
Else
    Debug "" + x + " is not greater than " + y
EndIf

;  OK:  No need to specify signedness when LHS and RHS are same signedness.
;       This means the vast majority of cases are transparently handed.
Define z.ul = x
If x = z
EndIf
Though, that's adding casts and a world of potential complexity, which probably can't be called "BASIC" and is a slight departure from the way existing type conversions (transparently) work.

Still, it'd be super useful in the handful of cases where unsigned types are desired, and is possibly the safest and clearest way it might be done. Because mixing signed and unsigned is generally rare, explicit satisfaction of safety measures would also be rarely necessary, but when they do kick-in they'd help avoid serious bugs.

(In my very humble opinion)

((Thank you for coming to my TED talk :lol:))
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3942
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: IfUnsigned a < b

Post by wilbert »

I would suggest using an unsigned compare procedure which returns -1, 0 or 1 depending on the result of the comparison.

Code: Select all

ProcedureC UL_CMP(a.l, b.l)
  CompilerIf Defined(PB_Backend_C, #PB_Constant) And #PB_Compiler_Backend = #PB_Backend_C
    !return ((unsigned long)v_a > (unsigned long)v_b) - ((unsigned long)v_a < (unsigned long)v_b);
  CompilerElse
    !xor eax, eax
    !xor edx, edx
    !mov ecx, [p.v_a]
    !cmp ecx, [p.v_b]
    !seta al
    !setb dl
    CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
      !sub rax, rdx
    CompilerElse
      !sub eax, edx
    CompilerEndIf
    ProcedureReturn    
  CompilerEndIf  
EndProcedure

ProcedureC UL_CMP_PTR(*a.Long, *b.Long)
  CompilerIf Defined(PB_Backend_C, #PB_Constant) And #PB_Compiler_Backend = #PB_Backend_C
    !return (*(unsigned long*)p_a > *(unsigned long*)p_b) - (*(unsigned long*)p_a < *(unsigned long*)p_b);
  CompilerElse
    CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
      !mov rax, [p.p_a]
      !mov rdx, [p.p_b]
      !mov ecx, [rax]
      !cmp ecx, [rdx]
      !seta al
      !setb dl
      !movzx eax, al
      !movzx edx, dl
      !sub rax, rdx
    CompilerElse
      !mov eax, [p.p_a]
      !mov edx, [p.p_b]
      !mov ecx, [eax]
      !cmp ecx, [edx]
      !seta al
      !setb dl
      !movzx eax, al
      !movzx edx, dl
      !sub eax, edx
    CompilerEndIf
    ProcedureReturn    
  CompilerEndIf  
EndProcedure

ProcedureC UQ_CMP(a.q, b.q)
  CompilerIf Defined(PB_Backend_C, #PB_Constant) And #PB_Compiler_Backend = #PB_Backend_C
    !return ((unsigned long long)v_a > (unsigned long long)v_b) - ((unsigned long long)v_a < (unsigned long long)v_b);
  CompilerElse
    !xor eax, eax
    !xor edx, edx
    CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
      !mov rcx, [p.v_a]
      !cmp rcx, [p.v_b]
      !seta al
      !setb dl
      !sub rax, rdx
    CompilerElse
      !mov ecx, [p.v_a+4]
      !cmp ecx, [p.v_b+4]
      !jne .cnt
      !mov ecx, [p.v_a]
      !cmp ecx, [p.v_b]
      !.cnt:
      !seta al
      !setb dl
      !sub eax, edx
    CompilerEndIf
    ProcedureReturn    
  CompilerEndIf  
EndProcedure

ProcedureC UQ_CMP_PTR(*a.Quad, *b.Quad)
  CompilerIf Defined(PB_Backend_C, #PB_Constant) And #PB_Compiler_Backend = #PB_Backend_C
    !return (*(unsigned long long*)p_a > *(unsigned long long*)p_b) - (*(unsigned long long*)p_a < *(unsigned long long*)p_b);
  CompilerElse
    CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
      !mov rax, [p.p_a]
      !mov rdx, [p.p_b]
      !mov rcx, [rax]
      !cmp rcx, [rdx]
      !seta al
      !setb dl
      !movzx eax, al
      !movzx edx, dl
      !sub rax, rdx      
    CompilerElse
      !mov eax, [p.p_a]
      !mov edx, [p.p_b]      
      !mov ecx, [eax+4]
      !cmp ecx, [edx+4]
      !jne .cnt
      !mov ecx, [eax]
      !cmp ecx, [edx]
      !.cnt:
      !seta al
      !setb dl
      !movzx eax, al
      !movzx edx, dl
      !sub eax, edx
    CompilerEndIf
    ProcedureReturn    
  CompilerEndIf  
EndProcedure


Dim n.l(4)
n(0) = $90000000
n(1) = $40000000
n(2) = $12344321
n(3) = $12341234
n(4) = $fffffffe

Debug "signed compare"
If n(0) < n(1)
  Debug "$"+Hex(n(0), #PB_Long) + " < $" + Hex(n(1), #PB_Long)
Else
  Debug "$"+Hex(n(0), #PB_Long) + " >= $" + Hex(n(1), #PB_Long)
EndIf
Debug ""

Debug "unsigned compare"
If UL_CMP(n(0), n(1)) < 0
  Debug "$"+Hex(n(0), #PB_Long) + " < $" + Hex(n(1), #PB_Long)
Else
  Debug "$"+Hex(n(0), #PB_Long) + " >= $" + Hex(n(1), #PB_Long)
EndIf
Debug ""



ImportC ""
  qsort(*base, num, size, *comparator)
EndImport

Debug "unsigned sorting"

qsort(@n(), ArraySize(n())+1, SizeOf(Long), @UL_CMP_PTR())
For i = 0 To ArraySize(n())
  Debug "$"+Hex(n(i), #PB_Long)
Next
Windows (x64)
Raspberry Pi OS (Arm64)
Post Reply