fork(1) download
  1. /*
  2.   TinyTableV3 のソート実装における文字列比較の問題点
  3. */
  4.  
  5. function compareString (string1, string2) {
  6. var number1, number2;
  7.  
  8. string1 = string1.toLowerCase();
  9. string2 = string2.toLowerCase();
  10.  
  11. number1 = parseFloat(string1.replace(/[,$]/g, ''));
  12. number2 = parseFloat(string2.replace(/[,$]/g, ''));
  13.  
  14. if (!isNaN(number1) && !isNaN(number2)) {
  15. string1 = number1;
  16. string2 = number2;
  17. }
  18.  
  19. number1 = Date.parse(string1);
  20. number2 = Date.parse(string2);
  21.  
  22. if (!isNaN(number1) && !isNaN(number2)) {
  23. string1 = number1;
  24. string2 = number2;
  25. }
  26.  
  27. return string1 > string2 ? 1 : (string2 > string1 ? -1 : 0);
  28. }
  29.  
  30. // 期待通りの値を返すパターン (*1)
  31. print('compareString()');
  32. print(compareString('10', '2')); // 1 (左の値がより大きい)
  33. print(compareString('3', '5')); // -1 (右の値がより大きい)
  34. print(compareString('abc', 'ABC')); // 0 (同値である)
  35.  
  36. // 挙動の怪しいパターン (*2)
  37. print(compareString('-1', '1')); // 実装依存の値を返す (0 = Google Chrome 7 / 1 = IE8, Firefox v3.6.12, Opera v10.62)
  38. print(Date.parse(-1)); // 実装依存の値を返す (Number型の値 = Google Chrome 7 / NaN = IE8, Firefox v3.6.12, Opera v10.62)
  39. print(Date.parse(1)); // 実装依存の値を返す (Number型の値 = Google Chrome 7 / NaN = IE8, Firefox v3.6.12, Opera v10.62)
  40. print(Date.parse(-1) === Date.parse(1)); // 実装依存の真偽値を返す (true = Google Chrome 7 / false = IE8, Firefox v3.6.12, Opera v10.62)
  41.  
  42. /*
  43.  
  44.   (*1) 正確には、例示した全てのパターンで実装依存の値を返す。
  45.   '10', 'ABC' などの文字列値は Date.prototype.toString, Date.prototype.toUTCString で解釈できないためである。(詳しくは後述参照)
  46.  
  47.  
  48.   (*2) ECMAScript 3, ECMAScript 5 の「15.9.4.2」に詳細が載っている。
  49.  
  50.   ------
  51.   15.9.4.2 Date.parse (string)
  52.   ...
  53.  
  54.   However, the expression
  55.  
  56.   Date.parse(x.toLocaleString())
  57.  
  58.   is not required to produce the same Number value as the preceding three expressions and, in general, the value produced by Date.parse is implementation-dependent when given any String value that does not conform to the Date Time String Format (15.9.1.15) and that could not be produced in that implementation by the toString or toUTCString method.
  59.  
  60.   http://s...content-available-to-author-only...b.com/es5-spec/#x15.9.4.2
  61.   ------
  62.  
  63.   ------
  64.   15.9.4.2 Date.parse (string)
  65.   ...
  66.   一般に、その実装の toString か toUTCString メソッドによって生成されえない文字列を与えられたときの Date.parse に生成される値は、実装依存である。
  67.   http://w...content-available-to-author-only...e.jp/~oz-07ams/prog/ecma262r3/15-9_Date_Objects.html#section-15.9.4.2
  68.   ------
  69.  
  70.   Date.prototype.toString, Date.prototype.toUTCString で解釈し得ない値が引数の取られたとき、戻り値は実装依存となる。
  71.   従って、Google Chrome 7 の挙動は誤りではなく、その他のブラウザの挙動も誤りではない。
  72.  
  73.   Date.parse を使うのなら、正しくパースできる値のみを指定すべきといえる。
  74.   そもそも、parseFloat でNumber方の値に変換できた場合には Date.parse を通す必要がない。
  75.   Number型の値であることは明確なのだから、そのまま比較すればよいはずだ。
  76. */
  77.  
  78. function compareString2 (string1, string2) {
  79. var number1, number2;
  80.  
  81. string1 = string1.toLowerCase();
  82. string2 = string2.toLowerCase();
  83.  
  84. number1 = parseFloat(string1.replace(/[,$]/g, ''));
  85. number2 = parseFloat(string2.replace(/[,$]/g, ''));
  86.  
  87. if (!isNaN(number1) && !isNaN(number2)) {
  88. string1 = number1;
  89. string2 = number2;
  90. } else {
  91. number1 = Date.parse(string1);
  92. number2 = Date.parse(string2);
  93.  
  94. if (!isNaN(number1) && !isNaN(number2)) {
  95. string1 = number1;
  96. string2 = number2;
  97. }
  98. }
  99.  
  100. return string1 > string2 ? 1 : (string2 > string1 ? -1 : 0);
  101. }
  102.  
  103. print('compareString2()');
  104. print(compareString2('10', '2')); // 1 (左の値がより大きい)
  105. print(compareString2('3', '5')); // -1 (右の値がより大きい)
  106. print(compareString2('abc', 'ABC')); // 0 (同値である)
  107. print(compareString2('-1', '1')); // -1 (右の値がより大きい)
Success #stdin #stdout 0.27s 214336KB
stdin
Standard input is empty
stdout
compareString()
1
-1
0
-1
NaN
NaN
false
compareString2()
1
-1
0
-1