[Estimated Reading Time: 4 minutes]

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.

Ta-dah!

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.

The similarities:

  • Boolean values in RTTI are of TypeKind tkEnumeration
  • GetEnumName/Value support Boolean types/values

The differences:

  • 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;

Oxygene Variations

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.

Conclusion

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.

10 thoughts on “(True = 1) and (True = ‘-1’) ?”

    1. Yes, of course. The example is contrived to explore the properties of the Boolean implementation in Delphi, not as a guide to best practice. 🙂

      FYI: I fixed your original comment. For future ref, to post code in comments you can use a tag

      [delphi]
      [/delphi]

      Around the “code block”.

    2. Working with external hardware it’s not unusual for me to be tempted to simply cast if the documentation suggests compatibility with a Delphi type.

      Dangerous, dangerous.

  1. Especially when doing integrations with other systems based on other languages this might cause issues. I’ve learned to always check for “false” instead of true. False is always 0..

    1. …except when anything not = 0 is an error code and therefore, by implication, False.

  2. You have just opened a *huge* can of worms I think.

    Boolean comparisons appear to be broken. The compiler doesn’t compare if the booleans are nonzero, it simply compares the integer values:

    procedure TForm1.Button1Click(Sender: TObject);
    var a,b:Boolean;
    begin
    a:=Boolean(1);
    b:=boolean(2);

    {Debugger shows a=true,b=true}

    if (a=b)
    then showmessage(‘both are true’)
    else showmessage(‘something dreadfully wrong’);
    end;

    1. The problem is not in Boolean comparison but instead in the fact that Delphi is using Boolean approach where any non-zero value is considered as True and only zero value is considered as False. This actually prevents direct comparison of two different Boolean values unless they are converted to Boolean system where there can only be two possible Boolean values (0 or 1). but doing that would require more CPU time and would this greatly decrease performance of your programs.

      1. Delphi always does byte-wise comparison of booleans, the sole exception being “if a then…”. Just check a disassembly to verify that.

        The ordinal value of “True” is 1 in Delphi. But if you store “true” in a variant and retrieve it back in an integer, the value is suddenly -1:

        procedure TForm1.Button1Click(Sender: TObject);
        var aVariant:Variant; 
              b:boolean; 
              i:integer;
        begin
          b:=True;
        
          i:=integer(b); //Cast Bool to Int
          Showmessage(format('In Delphi, TRUE is %d',[i]));
        
          aVariant:=b; // boolean -> variant
          i:=aVariant; // variant -> Int
        
          Showmessage(format('True is now suddenly %d',[i]));
        end;
        
  3. How a simple true/false switch can lead to two blogposts 🙂

Comments are closed.