Relational Operators

A project log for Ternary Computing Menagerie

A place for documenting the many algorithms, data types, logic diagrams, etc. that would be necessary for the design of a ternary processor.

Mechanical AdvantageMechanical Advantage 04/27/2019 at 07:310 Comments

The conventional relational operations compare numerical or boolean variables and return a boolean value based on the result of that comparison. The possible variations of relational operations are:

if a then...
if !a then...
if a > b then...
if a !<= b then...   (same as above)
if a < b then...
if a !>= b then...   (same as above)
if a >= b then...
if a !< b then...     (same as above)
if a <= b then...
if a !> b then...     (same as above)
if a == b then...
if a != b then...

Further, a couple of new relational operator would exist. The first is:

if a == NEUT then...

Using '?' to represent this operator is nicely consistent.

if a then...
if ?a then...
if !a then...

The second one is !! (ternary negation) which differs from boolean NOT. It returns FALSE for a positive input, returns NEUT for a 0 input and returns TRUE for a negative input.

In a boolean system some of these operations are redundant and aren't generally implemented in languages. These same redundancies exist in a balanced ternary system because the basic laws of arithmetic haven't changed.

The behavior of the relational operators is well defined in each language for booleans and integers, but needs to be explored when comparing a boolean and a kleene. As mentioned in other posts, I favor booleans that are forced to contain only a 0 or 1 rather than the "soft" booleans where any non-zero value is TRUE. This is how Python and stdbool.h in C do it. I also favor expanding this concept to the kleene data type so that only -1 is FALSE, only 0 is NEUTRAL and only 1 is TRUE. Any attempt to assign a kleene a different value would result in a negative value being clamped to -1 and a positive value being clamped to 1.

When comparing a kleene and a boolean there are a couple of things to consider. So far I have been considering that a boolean FALSE would continue to be represented by a 0 but I now realize this could cause serious problems such as:

bool x = FALSE          //contains 0
kleene y = FALSE      //contains -1

x                                 //returns FALSE
y                                 //returns FALSE

x > y                           //returns TRUE... uh oh...

x == -1                         //returns FALSE
y == -1                         //returns TRUE... dang

x == y                         //returns... FALSE? Let me think about it...

Now how about comparing a boolean to a kleene:

bool x = FALSE         //contains 0
kleene y = NEUT      //contains 0

x == y                        //returns TRUE... oh hell...

After some more consideration I now see that one of two things would have to happen. Either a boolean would have to use -1 as the FALSE value to avoid irrationalities like the ones demonstrated above, or the relational operators would have to do type checking and translate accordingly.

I favor the type checking approach rather than just asserting that -1 is the new boolean FALSE. This is because it would simply break too much code. Even the simplest things like "int x = 0" in C would break. So would any code that counts down to 0 and does comparisons to make a decision based on the countdown. Off-by-one errors would be everywhere. Therefore, type checking looks like the solution.

The next question is, if a boolean were compared to a kleene, what would the return type be? On one hand it could always be a boolean since every one of the above operators always resolves to either TRUE or FALSE. On the other hand, what would happen in a case like this:

boolean x = FALSE
kleene y = FALSE

x = x == y       //returns a TRUE... boolean?
y = x == y       //returns a TRUE... kleene?

This would definitely be language specific. Python doesn't have any problem with changing a variable's type depending on the situation. C++ just ignores you and leaves the variable type alone if you do something like trying to add 1.5 to an integer. In cases where the return value is not being assigned to an existing or newly instantiated variable then it would be fine to internally consider it a boolean in its limited scope. For example:

kleene x = FALSE
kleene y = FALSE

if x == y

      do something...


The datum returned by the == operator is going to exist temporarily and with limited scope. It would probably be safest to consider it a boolean since that is the more limited of the two options.

As for comparing a kleene and a kleene, the simple answer is that the operators behave exactly as one would logically expect, and return either TRUE or FALSE, but not NEUT.

kleene x = FALSE
kleene y = NEUT
kleene z = TRUE

print (x > y)


print (y >= x)


print (x < z)


That takes care of most of the relational operators but there is one more that is perfectly suited to a balanced ternary system. This is the "spaceship" operator that is generally denoted by <=> in the languages that support it. This is the "three-way" comparison. Let's say that the expression "a <=> b" is being evaluated. It returns an integer with a -1 if a is less than b, or it returns 0 if a is equal to b, or it returns 1 if it is greater than b. The only difference would be that in our hypothetical system it would return a kleene with a value of FALSE, NEUT, or TRUE.

if a <=> b == FALSE, then a < b
if a <=> b == NEUT, then a == b
if a <=> b == TRUE, then a > b

Relational Operations       Equivalent Three Way Operations
if a > b then...                     if a <=> b == TRUE then...
if a !<= b then...                  if a <=> b == TRUE then... (same as above)
if a < b then...                     if a <=> b == FALSE then...
if a !>= b then...                  if a <=> b == FALSE then... (same as above)
if a >= b then...                   if a <=> b != FALSE then...
if a !< b then...                    if a <=> b != FALSE then... (same as above)
if a <= b then...                   if a <=> b != TRUE then...
if a !> b then...                    if a <=> b != TRUE then... (same as above)
if a == b then...                   if a <=> b == NEUT then...
if a != b then...                    if a <=> b != NEUT then...

if a then...                           if a == TRUE then...

if ?a then...                         if a == NEUT then...

if !a then...                          if a == FALSE then...

I know that the three-way operations look more complex and appear to be equivalent, but there is a difference. Based on some work I've previously done on the appropriate status flags needed for a ternary processor, it is possible to do any of those comparisons in a single operation. On the binary side, some of those take two separate comparisons, which actually means: do the first comparison, store the result in a register, do the second comparison, store the result in a register, compare the two results for the final answer, jump/branch or don't based on the result. Using the three way operators it would simply be: do the comparison, jump/branch or don't based on the answer. Six operations turn into two.

Ultimately, I think it will be this kind of thing that defines the difference between binary and balanced ternary systems. You just get more computation out of each action. The more I research it, the more this becomes clear.