Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is "zzz" -le "~~~" false?

Tags:

powershell

As the title says, why does this happen?

PS C:\temp> "zzz" -le "~~~"
False

PS C:\temp> "~~~" -le "zzz"
True

"~" is the next to last ASCII character. I cannot understand a collation where it comes before "z".

like image 278
Ross Presser Avatar asked Dec 19 '22 10:12

Ross Presser


1 Answers

It's because ~ (tilde) is a diacritical mark, and the default comparison is linguistic comparison, not ordinal. Linguistic comparisons ignore diacritical marks when sorting. It's all a consequence of everything being in Unicode.

Try:

PS C:\> $x = @('aaa','~~~','zzz')
PS C:\> [System.Array]::Sort($x)
PS C:\> $x
~~~
aaa
zzz
PS C:\> [System.Array]::Sort($x,[System.StringComparer]::Ordinal)
PS C:\> $x
aaa
zzz
~~~

Similar answer in C# is here.

Here's a larger comparison using the en-US culture with some strings that have diacritical marks:

PS C:\> $x = @("0","9","a","A","á","Á","ab","aB","Ab","áb","Áb","Æ","z","Z","~")
PS C:\> [Array]::Sort($x)
PS C:\> $x
~
0
9
a
A
á
Á
ab
aB
Ab
áb
Áb
Æ
z
Z
PS C:\> [Array]::Sort($x,[StringComparer]::Ordinal)
PS C:\> $x
0
9
A
Ab
Z
a
aB
ab
z
~
Á
Áb
Æ
á
áb

So, which is correct? It's going to depend on your application, but the .Net Framework defaults to culture-based comparison.

As far as I'm aware, string comparison defaults to [System.StringComparer]::CurrentCulture for case sensitive and [System.StringComparer]::CurrentCultureIgnoreCase for case insensitive. I don't know any way to change this directly. Even using invariant culture doesn't seem to affect things:

PS C:\> [System.Threading.Thread]::CurrentThread.CurrentCulture = [System.Globalization.CultureInfo]::InvariantCulture
PS C:\> [System.Array]::Sort($x)
PS C:\> $x
~~~
aaa
zzz
PS C:\> $x[0] -le $x[1]
True

To force ordinal comparison, use System.String.CompareOrdinal:

[System.String]::CompareOrdinal($StringA,$StringB)

If the result is negative, then $StringA is less than $StringB.
If the result is zero, then $StringA equals $StringB.
If the result is positve, then $StringA is greater than $StringB.

Thus, this:

'zzz' -le '~~~'

Is equivalent with ordinal comparisons to:

PS C:\> [System.String]::CompareOrdinal('zzz','~~~') -le 0
True
like image 183
Bacon Bits Avatar answered Mar 21 '23 06:03

Bacon Bits