2

In C# 4.0, whenever I compare two strings which have one or more trailing slashes, the comparison gives incorrect results:

String a = "1/2.1/";
String b = "1/2/";
if (a.CompareTo(b) > 0)
    MessageBox.Show("Correct: " + a + " > " + b);
else
    MessageBox.Show("Not correct: " + a + " <= " + b);

a = a.TrimEnd('/');
b = b.TrimEnd('/');

if (a.CompareTo(b) > 0)
    MessageBox.Show("Trailing slash removed. Correct: " + a + " > " + b);
else
    MessageBox.Show("Trailing slash removed. Not correct: " + a + " <= " + b);

Lexically speaking, "1/2.1/" comes after "1/2/", and there is not much question about that.

This behaviour also occurs in other places, such as sorting a datatable using the Select method.

Am I doing something wrong? Or is this a bug in .Net? It should not even have anything to do with culture-specific information etc. since a slash is part of the most basic US ASCII character set.

I am running into this when comparing SQL Server hierarchyIDs. It's easy enough to solve but it is a somewhat astonishing problem.

nepdev
  • 937
  • 1
  • 11
  • 19
  • 2
    `.` is 0x2e, `/` is 0x2f. `.` comes first. Your problem is exactly in the *assumption* you took for granted: *"1/2.1/ comes after 1/2/, and there is not much question about that."* – Kobi Apr 12 '15 at 12:49
  • 2
    Note, that you are using culture sensitive comparison. Few people understand the Unicode comparison rules. There are many behaviors that you will find surprising. – usr Apr 12 '15 at 12:56
  • See also [Compare version numbers without using split function](http://stackoverflow.com/questions/7568147/compare-version-numbers-without-using-split-function) – xmojmr Apr 13 '15 at 08:49

3 Answers3

1

If my old skill in C doesn't fail me, I think that CompareTo executes a character by character subtraction of the Integer value of the characters until the result is not zero.

After the first 3 identical characters the CompareTo looks at the fourth character, and this is a point for the first string and a slash for the second string.

The integer value of the point character is 46 while the integer value of the slash is 47, 46-47 gives back -1 so "1/2.1/" is less than "1/2/".

Steve
  • 213,761
  • 22
  • 232
  • 286
1

Lexically speaking, "1/2.1/" comes after "1/2/", and there is not much question about that.

Why would it come after? On the ASCII chart, the / comes immediately after the ..

Given the following two strings, they're equal until you reach the 4th character. Then the / and . are compared, and the / is greater. So the result you're seeing (a < b) is actually correct.

1/2.1/
1/2/

After calling TrimEnd(), you end up with two different strings where and a > b.

1/2.1
1/2
Grant Winney
  • 65,241
  • 13
  • 115
  • 165
  • Thanks - of course you are right, C# performs a character-by-character code point comparison. Got tripped up by the common claim that SQL Server hierarchyID (string-converted) values are strictly in sequence - this is where these type of strings originates. Well, they are not in sequence then. – nepdev Apr 12 '15 at 12:58
  • This will work in the case where the strings are longer: `1/2.1/1/` and `1/2/1/` or if their first number differs: `1.1/2/` and `1/2/`. – Olivier Jacot-Descombes Apr 12 '15 at 13:15
1

You can compare strings containing numbers if the numbers are right aligned:

01/02.00/
01/02.10/
01/10.00/

If this is not possible, consider creating a type for your numbers

public class ChapterNumber :  IComparable<ChapterNumber>
{
    private readonly decimal[] _number;

    public ChapterNumber(params decimal[] number)
    {
        _number = number;
    }

    public int CompareTo(T obj)
    {
        var other = obj as ChapterNumber;
        if (other == null) {
            return +1;
        }
        int len = Math.Min(_number.Length, other._number.Length);
        for (int i = 0; i < len; i++) {
            int result = _number[i].CompareTo(other._number[i]);
            if (result != 0) {
                return result;
            }
        }
        return _number.Length.CompareTo(other._number.Length);
    }

    public override ToString()
    {
        return String.Join('/', _number) + "/";
    }
}

Usage:

var a = new ChapterNumber(1, 2.1m);
var b = new ChapterNumber(1, 2);
if (a.CompareTo(b) > 0) {
    ...
}
Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188