The ternary logic

published: 24 September 2020 / updated 7 October 2020

Lire cette page en français

 

Preamble

Let's get directly into the subject. Binary logic is two values: TRUE and FALSE. That's all.

It is the base from which are built all the binary logic through the operators OR, AND, NOT and the resulting functions in electronics, NOR, NAND, XOR....

So a ternary logic, why do it, if the binary logic responds so perfectly needs to?

Manage unknown cases

We will illustrate the ternary logic with two extremely concrete cases.

The first case is that of a sliding door, of this type of door that we find in all shopping centers:

Let's just take a clapper, the one on the right for example. We can consider that this clapper has two states:

To confirm each of these states, the manufacturer has fitted each leaf with a contactor limit switch confirming the state of the leaves. These contactors act on the motorization to stop this motorization as soon as one of these binary states is reached.

But the state of a leaf cannot be linked to these contactors alone. It exists the case where each contactor indicates TRUE, which is physically impossible for a leaf: it cannot be opened AND closed! ... except in quantum physics..

On the other hand, there is one case that happens all the time. When is it the two contactors indicate FALSE: the leaves are neither open, nor closed.

Binary logic cannot describe the state of a leaf which in this case is neither open nor closed.

In automatic mode, sequential logic knows how to manage perfectly without having to take into account a third state, ie a UNKNOW state.

The ternary logic of the SQL language

Here are two examples of the action of the logical operator AND in SQL language:

SELECT TRUE AND TRUE 
SELECT TRUE AND FALSE 

The SQL language includes a third state: NULL , which is similar to the state UNKNOW of our door leaf.

Still in SQL language, here are all the ternary logical combinations with the logical operator OR:

SELECT TRUE OR TRUE;    -- result: 1
SELECT TRUE OR NULL;    -- result: 1
SELECT TRUE OR FALSE;   -- result: 1
SELECT NULL OR TRUE;    -- result: 1
SELECT NULL OR NULL;    -- result: NULL
SELECT NULL OR FALSE;   -- result: NULL
SELECT FALSE OR TRUE;   -- result: 1
SELECT FALSE OR NULL;   -- result: NULL
SELECT FALSE OR FALSE;  -- result: 0
if there is no solution,
there is no problem.

Ternary logical operators

The idea is to reproduce, in FORTH language, the ternary equivalent of the operators AND, OR and NOT:

The ternary OR operator

Here is the table of ternary logic states for the operator OR:

A B A OR B
TrueTrueTrue
TrueUnknowTrue
TrueFalseTrue
UnknowTrueTrue
UnknowUnknowUnknow
UnknowFalseUnknow
FalseTrueTrue
FalseUnknowUnknow
FalseFalseFalse

In FORTH language, we have no logical UNKNOW state. We go therefore define numerical values and associate them with a logical equivalent ternary:

\ tfl : ternary flag
\ tfl = 0   is FALSE  flag
\ tfl = 1   is TRUE   flag
\ tfl = 2   is UNKNOW flag

According to this text:

Why this choice of 0, 1 and 2?

If we take any integer, 16 or 32 bits, simply hiding on bit b0, there are two possible states: 0 for an even value, 1 for an odd value.

If we mask TRUE (in binary 1111111111111111), we get 1 which remains TRUE in ternary logic. It will be the same for all other values odd integers.

We could have proceeded differently: 0 for FALSE, positive for TRUE and negative for UNKNOW.

Here is a video which explains ternary numeration (base 3). It is this video that has oriented our choice on the values 0 (FALSE), 1 (TRUE) and 2 (UNKNOW):

For the ternary mechanics to work in FORTH language, we go first create the word n>tfl:

\ convert decimal value to ternary flag 
: n>tfl ( n --- tfl) 
    dup  0= if            \ test if n equal 0 
        drop 0 
    else 
        dup  1 and 0> if  \ test if n is ODD 
            drop 1 
        else 
            drop 2        \ else n is EVEN 
        then 
    then 
  ; 

This word n> tfl is simple and efficient:

If we want to test two values, we will add their ternary values like this:

\ sum two ternary flags 
\ 0 2 on stack: 
  n>tfl 
  swap n>tfl   
  10 * + 

Thus, two ternary flags will merge into 9 possible values: 00, 01, 02, 10, 11, 12, 20, 21 and 22.

We can now define the ternary operator TOR (for Ternary OR):

\ ternary OR 
: tOR ( n1 n2 --- tfl) 
    n>tfl 
    swap n>tfl 
    10 * + 
    dup 11 =   if  drop 1 exit  then 
    dup 12 =   if  drop 1 exit  then 
    dup 10 =   if  drop 1 exit  then 
    dup 21 =   if  drop 1 exit  then 
    dup 22 =   if  drop 2 exit  then 
    dup 20 =   if  drop 2 exit  then 
    dup 01 =   if  drop 1 exit  then 
    dup 02 =   if  drop 2 exit  then 
        00 =   if       0 exit  then 
  ; 

There are certainly much more elegant methods. We will talk about this later. The interest of this definition is to show a readable way of create this operator tOR.

The ternary AND operator

Here is the table of ternary logic states for the AND operator:

A B A AND B
TrueTrueTrue
TrueUnknowUnknow
TrueFalseFalse
UnknowTrueUnknow
UnknowUnknowUnknow
UnknowFalseFalse
FalseTrueFalse
FalseUnknowFalse
FalseFalseFalse
\ ternary AND 
: tAND ( n1 n2 --- tfl) 
    n>tfl 
    swap n>tfl 
    10 * + 
    dup 11 =   if  drop 1 exit  then 
    dup 12 =   if  drop 2 exit  then 
    dup 10 =   if  drop 0 exit  then 
    dup 21 =   if  drop 2 exit  then 
    dup 22 =   if  drop 2 exit  then 
    dup 20 =   if  drop 0 exit  then 
    dup 01 =   if  drop 0 exit  then 
    dup 02 =   if  drop 0 exit  then 
        00 =   if       0 exit  then 
  ; 

The ternary NOT operator

Here is the table of ternary logic states for the NOT operator:

A NOT A
TrueFalse
UnknowUnknow
FalseTrue
\ ternary NOT 
: tNOT ( n1 --- tfl) 
    n>tfl 
    dup 1 =   if  drop 0 exit  then 
    dup 2 =   if  drop 2 exit  then 
        0 =   if  drop 1 exit  then 
  ; 

Other solutions

Gordon Charlton solution

: tOR ( tfl1 tfl2 -- tfl3) 
    or dup 2 > if 2 - then ; 

Branchless tOR:

: tOR  
    or dup 1+ 4 / 2* - ; 

Bruce R. McFarling solution

Though in my poor xForth, condemned to run on a 65C02, only the tOR would be faster... branching would be faster than multiplying, which is painfully slow.

Now, the only time {A,B,tAND} is not {A,B,AND} is when {A,B,+}>2 => {A,B,tAND}=2, so perhaps:

: tAND ( tfl1 tfl2 -- tfl3 ) 
  2DUP AND >R + 2 > 2 AND 
  R> OR ;