The Developer’s Cry

Yet another blog by a hobbyist programmer

BIT : The Boolean Integer Test

The previous post was about how I got bitten by an implicit type conversion in C, and how the compiler allowed comparing an integer with a boolean value. This made me wonder about other languages, how do they behave? And so, I explored a number of other programming languages, many of which I’ve never even used before. It’s interesting to see how they handle this particular case. For this we use a small program that I call the BIT test; the boolean integer test. In pseudo code:

# BIT : Boolean Integer Test

a = -1
b = true

if (a) {
    print "using integer as truth value"
}
if (a == b) {
    print "comparing integer and bool : true"
} else {
    print "comparing integer and bool : fail"
}

The first if-statement tests whether integer values can be used as truth value. This assumes that -1 implies TRUE, which is usually the case. [Of course, usually non-zero implies true, but that’s not exactly what we are testing for here]. What also may happen is that the compiler bails out with an error; an integer is not a boolean. I’m actually fine with both behaviors of this case (either compile or error out), I just think it’s interesting when a language is strict enough to flag an error for using an integer as truth value.

The second if-statement is more subtle. An integer value of -1 is not equal to true, therefore some languages will produce fail. You can (and may) argue that “fail” should actually read “fine”. My confusion is complete however, because iff in the first if-statement a evaluated as true, then why does the second if-statement not print true…? In a lot of languages however, the second if-condition gives an error and does not compile; cannot compare type integer with type bool. I actually prefer this strict behavior; that’s what strong typing is about.

Following is a list of languages and their BIT test program.

C

/*
    bit.c - boolean integer test
*/

#include <stdbool.h>
#include <stdio.h>

int main(void) {
    int a = -1;
    bool b = true;

    if (a) {
        printf("using integer as truth value\n");
    }
    if (a == b) {
        printf("comparing integer and bool : true\n");
    } else {
        printf("comparing integer and bool : fail\n");
    }
    return 0;
}

The C program compiles and outputs:

using integer as truth value
comparing integer and bool : fail

C++

The C++ program is the same as the C program, except that we include <cstdio>. It also behaves the same.

C3

module output;  // necessary for compiler explorer

import std::io;

fn void main() {
    int a = -1;
    bool b = true;

    if (a) {
        io::printfn("using integer as truth value");
    }
    if (a == b) {
        io::printfn("comparing integer with bool : true");
    } else {
        io::printfn("comparing integer with bool : fail");
    }
}

The second if-statement does not compile.

Go

package main

import "fmt"

func main() {
    a := -1
    b := true

    if a {
        fmt.Println("using integer as truth value")
    }
    if a == b {
        fmt.Println("comparing integer with bool : true")
    } else {
        fmt.Println("comparing integer with bool : fail")
    }
}

Both if-statements do not compile.

Swift

import Foundation

let a = -1
let b = true

if (a) {
    print("using integer as truth value")
}
if (a == b) {
    print("comparing integer with boolean : true")
} else {
    print("comparing integer with boolean : fail")
}

Both if-statements do not compile.

Zig

const std = @import("std");
const builtin = @import("builtin");

pub fn main() void {
    const a: i32 = -1;
    const b: bool = true;

    if (a) {
        std.debug.print("using integer as truth value");
    }

    if (a == b) {
        std.debug.print("comparing integer with bool : true");
    } else {
        std.debug.print("comparing integer with bool : fail");
    }
}

Both if-statements do not compile.

Odin

package main

import "core:fmt"

main :: proc() {
    a := -1
    b := true

    if a {
        fmt.printf("using integer as truth value\n")
    }
    if a == b {
        fmt.printf("comparing integer and bool : true\n")
    } else {
        fmt.printf("comparing integer and bool : fail\n")
    }
}

Both if-statements do not compile.

Rust

fn main() {
    let a = -1;
    let b = true;

    if a {
        println!("using integer as truth value");
    }
    if a == b {
        println!("comparing integer with bool : true");
    } else {
        println!("comparing integer with bool : fail");
    }
}

Both if-statements do not compile.

Carbon

package BIT api;

fn Main() -> i32 {
    var a: i32 = -1;
    var b: bool = true;

    if (a) {
        Print("using integer as truth value");
    }
    if (a == b) {
        Print("comparing integer to bool : true");
    } else {
        Print("comparing integer to bool : fail");
    }
    return 0;
}

Both if-statements do not compile.

C#

using System;

class Program
{
    static void Main() {
        int a = -1;
        bool b = true;

        if (a) {
            Console.WriteLine("using integer as truth value");
        }
        if (a == b) {
            Console.WriteLine("comparing integer with bool : true");
        } else {
            Console.WriteLine("comparing integer with bool : fail");
        }
    }
}

Both if-statements do not compile.

Java

class Test
{
    public static void main(String []args)
    {
        int a = -1;
        boolean b = true;

        if (a) {
            System.out.println("using integer as truth value");
        }
        if (a == b) {
            System.out.println("comparing integer and boolean : true");
        } else {
            System.out.println("comparing integer and boolean : fail");
        }
    }
};

Both if-statements do not compile.

Scala

@main def run() =
    val a: Int = -1
    val b: Boolean = true

    if (a) {
        println("using integer as truth value")
    }
    if (a == b) {
        println("comparing integer and boolean : true")
    } else {
        println("comparing integer and boolean : fail")
    }

Both if-statements do not compile.

Kotlin

fun main() {
    val a = -1
    val b = true

    if (a) {
        println("using integer as truth value")
    }
    if (a == b) {
        println("comparing integer and bool : true")
    } else {
        println("comparing integer and bool : fail")
    }
}

Both if-statements do not compile.

JavaScript

let a = -1
let b = true

if (a) {
    console.log("using integer as truth value")
}
if (a == b) {
    console.log("comparing integer to bool : true")
} else {
    console.log("comparing integer to bool : fail")
}

This outputs:

using integer as truth value
comparing integer to bool : fail

TypeScript

The BIT test program in TypeScript is the same as JavaScript. It also produces the same output when run. TypeScript does report an error for the second if-statement.

Perl

my $a = -1;
my $b = true;

if ($a) {
    print("using integer as truth value\n");
}

if ($a == $b) {
    print("comparing integer and bool : true\n");
} else {
    print("comparing integer and bool : fail\n");
}

This outputs:

using integer as truth value
comparing integer and bool : fail

Ruby

a = -1
b = true

if a then
    puts "using integer as truth value"
end

if a == b then
    puts "comparing integer and boolean : true"
else
    puts "comparing integer and boolean : fail"
end

This outputs:

using integer as truth value
comparing integer and boolean : fail

Lua

a = -1
b = true

if a
then
    print("using integer as truth value")
end

if a == b
then
    print("comparing integer and bool : true")
else
    print("comparing integer and bool : fail")
end

This outputs:

using integer as truth value
comparing integer and bool : fail

Python

if __name__ == '__main__':
    a = -1
    b = True

    if a:
        print("using integer as truth value")

    if a == b:
        print("comparing integer with bool : true")
    else:
        print("comparing integer with bool : fail")

This outputs:

using integer as truth value
comparing integer with bool : fail

Mojo

def main():
    a = -1
    b = True

    if a:
        print("using integer as truth value")

    if a == b:
        print("comparing integer to boolean : true")
    else:
        print("comparing integer to boolean : fail")

The second if-statement does not compile.

Nim

let
  a = -1
  b = true

if a:
  echo "integer used as truth value"

if a == b:
  echo "comparing integer and bool : true"
else:
  echo "comparing integer and bool : fail"

Both if-statements do not compile.

Julia

a = -1
b = true

if a
    println("using integer as truth value")
end

if a == b
    println("comparing integer with bool : true")
else
    println("comparing integer with bool : fail")
end

Julia does not compile the first if-statement. But it does compile the second, and then branches to “fail”.

Pascal

program bit;
var
    a : integer = -1;
    b : boolean = TRUE;

begin
    if a then
        writeln('using integer as truth value');
    if a == b then
        writeln('comparing integer with bool : true');
    else
        writeln('comparing integer with bool : fail');
end.

Both if-statements do not compile.

Ada

with Ada.Text_IO;

procedure Bit is
    a : Integer := -1;
    b : Boolean := True;

begin
    if a then
        Ada.Text_IO.Put_Line("using integer as truth value");
    end if;

    if a == b then
      Ada.Text_IO.Put_Line("comparing integer with bool : true");
    else
      Ada.Text_IO.Put_Line("comparing integer with bool : fail");
    end if;
end Bit;

Both if-statements do not compile.

Fortran 90

PROGRAM bit
    INTEGER :: a = -1
    LOGICAL :: b = .TRUE.

    IF (a) THEN
        print *, 'using integer as truth value'
    END IF
    IF (a == b) THEN
        print *, 'comparing integer and bool : true'
    ELSE
        print *, 'comparing integer and bool : fail'
    END IF
END PROGRAM

Both if-statements do not compile.

FORTH

The FORTH language does not feature a boolean type, and thus the BIT test can not be ported to FORTH. Over there, the whole issue is a non-issue; does not apply. It’s fascinating if you think about why this is … FORTH is close to assembly language, and consequently in the design of FORTH there is no such thing as a “bool”.

Strong versus weak

The bool found in higher-level programming languages is a conceptual thing, that only exists in the mind of the compiler, so to speak. But if that is so, then how do compilers compile bool? Right, it gets treated as an integer in disguise. And so it happens that some languages mix up the difference between booleans (value either true or false) and integers (which span a range of whole numbers, positive or negative, including zero).

Languages that allow comparing integers and booleans are typically dynamically typed scripting languages. It’s rather odd that the statically typed C/C++ exhibit the behavior that they do. Strong typing is nothing new; many older languages that came before C already treated the comparison of integer and bool as an error.

The Julia language is unique in that it allows the comparison, but using an integer as truth value is an error. This is peculiar, and it almost feels like a bug.

Another notable case is Python. Python is dynamically typed, but it features strong type annotations—which are basically comments, but you can run a separate type checker. Moreover, the language features a TypeError exception that is raised when you are using the wrong type (!) but it does not raise for this case.

C and Python are easily my two favorite programming languages. I like their mild typing; kind of strong, but also weak. But even so, I strongly feel that not signalling the error, is an error. The BIT test shows that not all languages behave in the same way. That’s confusing; just like the laws of physics apply everywhere, arithmetics work the same everywhere, and boolean logic is rock solid no matter where you go. Yet there is no universal rule for comparison between integer and boolean in programming.