5

Many languages define that compare functions should return ANY negative value, zero, or ANY positive value. Is there some reason that it shouldn't be clearly defined as -1 0 and 1? Does a wide range in possible return values help more advanced algorithms to work more efficiently? If so, what algorithms work this way?

4
  • 5
    I don't think ambiguous is the right term. None of the ranges overlap so the meaning of the return value should always be clear. Jan 13, 2015 at 8:42
  • 1
    Indeed, these aer loose requirements, not ambiguous.
    – MSalters
    Jan 13, 2015 at 10:38
  • 6
    Note that they are magic numbers. Even worse, they not only shouldn't be magic, they shouldn't even be numbers! What does it mean to raise "equal" to the "less-thanth" power? In Haskell, and many other languages, the return value is an enum Eq | Lt | Gt. Yet another thing that is wrong with the above definition, at least in languages with universal comparability, is that it assumes a total ordering, IOW, there is no way to indicate "these two values can't be ordered". You actually would need an enum Eq | Lt | Gt | Neither. Jan 13, 2015 at 13:06
  • My suspicion is this is all ultimately inherited from C, hence prevalent in languages that have some C heritage but not in others. Considering C it makes the simple implementation arguments present below look right as overflow is UB in C anyway
    – jk.
    Jul 13, 2015 at 9:28

4 Answers 4

7

If you allow comparison function to return any negative value instead of exactly -1, this can make for a simpler implementation. For instance, you can write

return this.position - that.position;

instead of having to write

if(this.position == that.position) {
    return 0;
}
else if(this.position < that.position) {
    return -1;
} else
    return 1;
}

(The alternative is to use an operator, like Perl's <=>, that generates exactly -1, 0, or 1. But it's easier to define a lenient API than to get a new operator into your language, unless you're Larry Wall.)

3
  • 3
    To add a very important point: As a rule of thumb arithmetic operations are almost faster than if-statements on almost every hardware. That's because if-statements need more complex binary code, namely the jump statements in assembler code are very time consuming. In other words the processing unit has to handle more complex statements and therefore to touch more statements internally.
    – shylynx
    Jan 13, 2015 at 10:31
  • The other important thing to note is that while this implementation of comparison is both simpler and faster than the version that must return specific values, the processor can check for negative, zero or positive conditions just as easily and efficiently as it can for specific values, so taken over both the calculation and the use of the comparison there is a net gain of efficiency.
    – Jules
    Jan 13, 2015 at 20:27
  • Note that this approach only works if the type of position is smaller than the return type of the comparison function. For example in Java, Integer.MAX_VALUE is bigger than -1, but Integer.MAX_VALUE - (-1) is negative due to overflow. Jan 14, 2015 at 9:35
3

For simple use cases it allows for a very trivial implementation:

public int compare(Child a, Child b) {
    return a.age - b.age;
}
//sort children by age to determine who babysits 

Meanwhile if more complex logic is required it is still easy to return the magic numbers -1, 0, or 1 once order is determined.


As CodesInChaos says in the comments the subtraction method fails to accommodate any overflow that may occur. General purpose libraries require greater robustness and complexity in their comparisons.

Here are a couple of a battle tested implementations:

  1. Java's Integer.compare():

    public static int compare(int x, int y) {
        return (x < y) ? -1 : ((x == y) ? 0 : 1);
    }
    
  2. Mono's Int32.CompareTo():

    public int CompareTo (object value)
    {
        if (value == null)
            return 1;
    
        if (!(value is System.Int32))
            throw new ArgumentException (Locale.GetText ("Value is not a System.Int32"));
    
        int xv = (int) value;
        if (m_value == xv)
            return 0;
        if (m_value > xv)
            return 1;
        else
            return -1;
    }
    

As you can see both of these apply the required logic then return the appropriate magic number.

6
  • 1
    Which won't work in most languages due to integer overflows. Similarly negating the value to reverse the ordering doesn't work for two's complement integers. Jan 13, 2015 at 11:01
  • @CodesInChaos Very true, I've clarified that it is a toy example. Jan 13, 2015 at 15:38
  • @CodesInChaos Got any children older than a billion years? Combined with one who is younger than minus a billion? Yes, then you can worry about overflows on the subtractions. I don't understand your comment on the negating.
    – user949300
    Jan 13, 2015 at 15:46
  • @user949300 1) Why would you write specific comparison code for the age of humans? You write that code once, so it's correct for every integer, and then reuse it. For example for the Child type, you'd write something like return child1.Age.CompareTo(child2.Age) where int.CompareTo needs to handle every integer. 2) -int.MinValue == int.MinValue, so negating doesn't change the sign for this case. Jan 13, 2015 at 16:08
  • 1
    The fact that in the general case overflow may occur does not mean this technique cannot be used; there are many specific cases where it is valid: comparing chars (on systems where sizeof(char) < sizeof(int), which is mostly true) is probably the most important.
    – Jules
    Jan 13, 2015 at 20:32
0

Return value semantics has almost nothing to do with implementation. This specification leaves no space for unspecified behavior and is statically verifiable.

If return value is specified to be an arbitrary number, this can checked statically - compiler can easily verify that function doesn't return string. Otherwise static verification is impossible - values that are outside of given range can't be eliminated statically for non-enum types. Therefore calling code will have to deal with unspecified values in runtime, emitting more unspecified behavior.

We can safely say that this decision is made to mimic behavior Jörg described in his comment - complete specification, every option covered.

-1

It's because when you do a compare you are usually just taking the difference of two values and instead of wasting more time and effort into mapping the result to {-1,0,1}, just leave the result and define the function as so. For example x.comparedTo(y) = 0 means that if x and y are both 5, (5 - 5) = 0 but if x = 100 and y = 150, (100 - 150) = -50 and so x < y.

1
  • 2
    this seems to merely repeat point made (and better explained) in prior answer
    – gnat
    Jan 13, 2015 at 9:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.