Skip to content

Commit 8b258f6

Browse files
committed
ext/standard/array.c: handle NaN in SORT_REGULAR using totalOrder
Apply IEEE 754 totalOrder predicate for NaN handling in transitive SORT_REGULAR comparisons. This provides a consistent, deterministic ordering where NaN values sort after +INF but before non-numeric strings: -INF < finite numbers < +INF < NaN < non-numeric strings
1 parent 5a7de5a commit 8b258f6

File tree

2 files changed

+78
-8
lines changed

2 files changed

+78
-8
lines changed

ext/standard/array.c

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -544,12 +544,24 @@ static int php_array_compare_transitive(zval *op1, zval *op2)
544544
return Z_LVAL_P(op1) > Z_LVAL_P(op2) ? 1 : (Z_LVAL_P(op1) < Z_LVAL_P(op2) ? -1 : 0);
545545

546546
case PHP_ARRAY_TYPE_PAIR(IS_DOUBLE, IS_LONG):
547+
if (UNEXPECTED(zend_isnan(Z_DVAL_P(op1)))) {
548+
return 1; /* NaN sorts after all integers (totalOrder) */
549+
}
547550
return ZEND_THREEWAY_COMPARE(Z_DVAL_P(op1), (double) Z_LVAL_P(op2));
548551

549552
case PHP_ARRAY_TYPE_PAIR(IS_LONG, IS_DOUBLE):
553+
if (UNEXPECTED(zend_isnan(Z_DVAL_P(op2)))) {
554+
return -1; /* integers sort before NaN (totalOrder) */
555+
}
550556
return ZEND_THREEWAY_COMPARE((double) Z_LVAL_P(op1), Z_DVAL_P(op2));
551557

552558
case PHP_ARRAY_TYPE_PAIR(IS_DOUBLE, IS_DOUBLE):
559+
if (UNEXPECTED(zend_isnan(Z_DVAL_P(op1)))) {
560+
return zend_isnan(Z_DVAL_P(op2)) ? 0 : 1; /* NaN sorts last among doubles (totalOrder) */
561+
}
562+
if (UNEXPECTED(zend_isnan(Z_DVAL_P(op2)))) {
563+
return -1; /* NaN sorts last among doubles (totalOrder) */
564+
}
553565
return ZEND_THREEWAY_COMPARE(Z_DVAL_P(op1), Z_DVAL_P(op2));
554566

555567
case PHP_ARRAY_TYPE_PAIR(IS_ARRAY, IS_ARRAY):
@@ -587,17 +599,15 @@ static int php_array_compare_transitive(zval *op1, zval *op2)
587599
return -zend_compare_long_to_string_ex(Z_LVAL_P(op2), Z_STR_P(op1), true);
588600

589601
case PHP_ARRAY_TYPE_PAIR(IS_DOUBLE, IS_STRING):
590-
if (zend_isnan(Z_DVAL_P(op1))) {
591-
return 1;
602+
if (UNEXPECTED(zend_isnan(Z_DVAL_P(op1)))) {
603+
return -1; /* NaN sorts before non-numeric strings (totalOrder) */
592604
}
593-
594605
return zend_compare_double_to_string_ex(Z_DVAL_P(op1), Z_STR_P(op2), true);
595606

596607
case PHP_ARRAY_TYPE_PAIR(IS_STRING, IS_DOUBLE):
597-
if (zend_isnan(Z_DVAL_P(op2))) {
598-
return 1;
608+
if (UNEXPECTED(zend_isnan(Z_DVAL_P(op2)))) {
609+
return 1; /* non-numeric strings sort after NaN (totalOrder) */
599610
}
600-
601611
return -zend_compare_double_to_string_ex(Z_DVAL_P(op2), Z_STR_P(op1), true);
602612

603613
case PHP_ARRAY_TYPE_PAIR(IS_OBJECT, IS_NULL):
@@ -660,6 +670,12 @@ static zend_always_inline int php_array_data_compare_regular_unstable_i(Bucket *
660670
}
661671

662672
if (EXPECTED(Z_TYPE_P(op1) == IS_DOUBLE && Z_TYPE_P(op2) == IS_DOUBLE)) {
673+
if (UNEXPECTED(zend_isnan(Z_DVAL_P(op1)))) {
674+
return zend_isnan(Z_DVAL_P(op2)) ? 0 : 1; /* NaN sorts last among doubles (totalOrder) */
675+
}
676+
if (UNEXPECTED(zend_isnan(Z_DVAL_P(op2)))) {
677+
return -1; /* NaN sorts last among doubles (totalOrder) */
678+
}
663679
return ZEND_THREEWAY_COMPARE(Z_DVAL_P(op1), Z_DVAL_P(op2));
664680
}
665681

ext/standard/tests/array/sort/sort_variation_numeric_strings.phpt

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,17 @@ $a = ["1e2", "1E2", "1e+2", "1e-2", "100"];
6464
sort($a, SORT_REGULAR);
6565
var_dump($a);
6666

67-
echo "\n-- Test 13: Consistency check --\n";
67+
echo "\n-- Test 13: NaN and INF float values (totalOrder) --\n";
68+
$a = [3.5, NAN, 1, "apple", NAN, 2.0, "10", -INF, INF];
69+
sort($a, SORT_REGULAR);
70+
var_dump($a);
71+
72+
echo "\n-- Test 14: NaN and INF float values reversed --\n";
73+
$a = [3.5, NAN, 1, "apple", NAN, 2.0, "10", -INF, INF];
74+
rsort($a, SORT_REGULAR);
75+
var_dump($a);
76+
77+
echo "\n-- Test 15: Consistency check --\n";
6878
$results = [];
6979
for ($i = 0; $i < 3; $i++) {
7080
$a = ["", "0", "00", "A"];
@@ -236,6 +246,50 @@ array(5) {
236246
string(3) "100"
237247
}
238248

239-
-- Test 13: Consistency check --
249+
-- Test 13: NaN and INF float values (totalOrder) --
250+
array(9) {
251+
[0]=>
252+
float(-INF)
253+
[1]=>
254+
int(1)
255+
[2]=>
256+
float(2)
257+
[3]=>
258+
float(3.5)
259+
[4]=>
260+
float(INF)
261+
[5]=>
262+
float(NAN)
263+
[6]=>
264+
float(NAN)
265+
[7]=>
266+
string(2) "10"
267+
[8]=>
268+
string(5) "apple"
269+
}
270+
271+
-- Test 14: NaN and INF float values reversed --
272+
array(9) {
273+
[0]=>
274+
string(5) "apple"
275+
[1]=>
276+
string(2) "10"
277+
[2]=>
278+
float(NAN)
279+
[3]=>
280+
float(NAN)
281+
[4]=>
282+
float(INF)
283+
[5]=>
284+
float(3.5)
285+
[6]=>
286+
float(2)
287+
[7]=>
288+
int(1)
289+
[8]=>
290+
float(-INF)
291+
}
292+
293+
-- Test 15: Consistency check --
240294
All runs produce same result: yes
241295
Done

0 commit comments

Comments
 (0)