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

))