It has been observed that the Delphi documentation states that the constants True and False have the values 1 and 0 respectively, not the -1 and 0 that the default string conversions apply. This does actually make sense but also lays a trap for the unwary.
True, True, Wherefore Art Thou ‘-1’ ?
In the Simple Types documentation it is stated that any non-zero value is logically true. But in the documentation for the Boolean type itself it is stated that only ordinal value 1 is considered true.
But if True has the explicit value 1 why does BoolToStr(True) return ‘-1’ and not simply ‘1’ ?
Well, in one key respect the documentation for the Boolean type is simply wrong.
In fact, any non-zero value is logically true, not just 1. i.e. the Simple Types documentation is correct on this point, as we can easily demonstrate:
procedure TestBoolean; var b: Boolean; begin b := Boolean(42); if b then ShowMessage('42 is True!'); end;
Run this and you will see the message.
And this leads us to an understanding of why BoolToStr(True) might reasonably return ‘-1’.
Regardless of the specific size of Boolean type involved (Byte, Word, Long etc), any non-zero value must have at least one bit set and may have more than one bit set. So it makes a certain amount of sense to say that a logical True is equivalent to a value where all possible bits are set, since any of them could be for any particular logically true value.
And in Two’s Complement notation, an integer with all bits set has the value -1.
That’s one way to think about it. But the Two’s Complement rationale also applies when considering Booleans as strictly, well, Boolean. 🙂
False has ordinality 0 and since True is the logical negation of False then if we flip all the bits in any sized value of False (0) then we again end up with all bits set which in Two’s Complement again yields -1.
A Question for the Ages…
Perhaps the real question is why True was ever defined with ordinality of 1 in the first place, and not -1 ?
I honestly don’t know, but suspect that it likely goes back to the origins of Pascal in the 1970’s and the introduction of Boolean as a formal enumerated type, thus determining the ordinality of the members as 0 and 1.
This might also explain some of the enum-like capabilities/compatibility of Boolean in Delphi, whilst clearly not behaving as an enum in some key respects.
- Boolean values in RTTI are of TypeKind tkEnumeration
- GetEnumName/Value support Boolean types/values
- GetEnumName/Value support Boolean types/values as special cases
- In more recent versions of Delphi, Boolean types support ToString() (enumerated types do not)
- Coercing an ordinal value in an enum that is not a defined member yields an out of bounds value (not an error, just an “unnamed” or undefined member)
- Coercing an ordinal value in a Boolean that is not a defined member yields a logical member (True)
Also worth noting is that GetEnumName/Value also support Integer types, so those old TypInfo “Enum” functions are obviously not just for enumerated types.
In addition, in one branch of the special case handling for Boolean, GetEnumValue explicitly yields -1 for True values, despite the documentation (and presumably the compiler) using 1. i.e. this particular enum function contrives to contradict the “definition” of this particular enum.
It’s A Trap!
The trap I mentioned at the beginning arises from a compiler behaviour and a rather misleading (at best, incorrect at worst) statement in the Simple Types documentation:
A [Boolean] is considered True when its ordinality is non-zero. If such a value appears in a context where a Boolean is expected, the compiler automatically converts any value of nonzero ordinality to True.
The problem is that inference that non-zero values are “automatically converted” to the value True, which we are told has ordinality of 1.
This is not always the case.
Again, we can demonstrate this very easily with a small change to that previous snippet of code:
procedure TestBoolean; var b: Boolean; begin b := Boolean(42); if b then ShowMessage('42 is True!'); if b = True then ShowMessage('b = True!'); end;
In this case, you will not see the second message.
The Boolean evaluates as logically true but it does not have the same ordinal value as True. The compiler does not convert the Boolean value with ordinality 42 to the value True (1) at all.
Things get even more interesting if you start changing the ordinality of the Boolean in order to figure out exactly what the compiler does consider to be equal to True. So, for example, to test that the compiler really is using ordinal 1 for True:
procedure TestBoolean; var b: Boolean; begin b := Boolean(1); // Or 1 or any b < 0 if b then ShowMessage('b is True!'); if b = True then ShowMessage('b = True!'); // Is shown for b = 1 and all b < 0 but not b > 1 end;
Plugging in some additional values, it turns out that b = True for b = 1 and b < 0 but not b > 1.
The trap that this lays is that you might be forgiven for thinking that explicit tests for equality with Boolean constants True and/or False are simply redundant noise, but in fact in the case of comparisons with True this can obviously introduce subtle but significant bugs.
Delphi Language Feature: Truthiness
In Delphi, you can even appear to have extremely true values:
procedure TestBoolean; var b: Boolean; begin b := Boolean(42); if b then ShowMessage('42 is True!'); if b = True then ShowMessage('b = True!'); if b > True then ShowMessage('b is more true than True itself!!'); end;
For developers using the Oxygene implementation of ObjectPascal I would note at this point that the same behaviour w.r.t = applies on .net but on Java b = true is just as logically true in this case as b itself.
procedure TestBoolean; begin var b := Boolean(42); if b then WriteLn('42 is True!'); // Yes it is if b = True then WriteLn('b = True!'); // Not on .net, but yes indeed on Java if b > True then // ERR: Does not compute! WriteLn('b is more true than True itself!!'); end;
I don’t know whether this equality comparison difference is an edge-case bug in the compiler or simply a consequence of variations in the type systems on the underlying platforms and I haven’t (yet) tested on Cocoa.
However, in all cases the Oxygene compiler will reject the use of the > operator on Boolean types, so you cannot fall into that particular aspect of the trap with Oxygene.
The seeming schizophrenia of True identifying as both 1 and -1 is not as crazy as it might at first seem, but when it comes to directly comparing for equality with True the safest approach is simply don’t.