[pecl-math-stats] fix/stats_stat_correlation: Fix stats_stat_correlation() returning near-zero residuals on ARM64

From: Date: Tue, 21 Apr 2026 03:04:57 +0000
Subject: [pecl-math-stats] fix/stats_stat_correlation: Fix stats_stat_correlation() returning near-zero residuals on ARM64
Groups: php.pecl.cvs 
Request: Send a blank email to pecl-cvs+get-28545@lists.php.net to get a copy of this message
Author: Rasmus Lerdorf (rlerdorf) Date: 2026-04-20T23:04:41-04:00 Commit: https://github.com/php/pecl-math-stats/commit/65a16409cb346c4667d79a2c2dd02bb74e78a5f6 Raw diff: https://github.com/php/pecl-math-stats/commit/65a16409cb346c4667d79a2c2dd02bb74e78a5f6.diff Fix stats_stat_correlation() returning near-zero residuals on ARM64 Changed paths: A tests/stats_stat_correlation_degenerate.phpt M php_stats.c Diff: diff --git a/php_stats.c b/php_stats.c index 5846efb..0e63488 100644 --- a/php_stats.c +++ b/php_stats.c @@ -3260,14 +3260,12 @@ PHP_FUNCTION(stats_stat_correlation) int ynum = 0; double sx = 0.0; double sy = 0.0; - double sxx = 0.0; - double syy = 0.0; - double sxy = 0.0; double mx; double my; - double vx; - double vy; - double cc; + double vx = 0.0; + double vy = 0.0; + double cc = 0.0; + double dx, dy; double rr; if (zend_parse_parameters(ZEND_NUM_ARGS(), "z/z/", &arg1, &arg2) == FAILURE) { @@ -3285,6 +3283,12 @@ PHP_FUNCTION(stats_stat_correlation) RETURN_FALSE; } + if (xnum < 2) { + php_error_docref(NULL, E_WARNING, "Correlation requires at least 2 data points"); + RETURN_FALSE; + } + + /* First pass: compute means */ zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(arg1), &pos1); zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(arg2), &pos2); @@ -3294,11 +3298,8 @@ PHP_FUNCTION(stats_stat_correlation) convert_to_double_ex(data1); convert_to_double_ex(data2); - sx += Z_DVAL_P(data1); - sxx += Z_DVAL_P(data1) * Z_DVAL_P(data1); - sy += Z_DVAL_P(data2); - syy += Z_DVAL_P(data2) * Z_DVAL_P(data2); - sxy += Z_DVAL_P(data1) * Z_DVAL_P(data2); + sx += Z_DVAL_P(data1); + sy += Z_DVAL_P(data2); zend_hash_move_forward_ex(Z_ARRVAL_P(arg1), &pos1); zend_hash_move_forward_ex(Z_ARRVAL_P(arg2), &pos2); @@ -3306,11 +3307,35 @@ PHP_FUNCTION(stats_stat_correlation) mx = sx / xnum; my = sy / ynum; - vx = sxx - (xnum * mx * mx); - vy = syy - (ynum * my * my); - cc = sxy - (xnum * mx * my); + + /* Second pass: compute variance and covariance from deviations */ + zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(arg1), &pos1); + zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(arg2), &pos2); + + while ((data1 = zend_hash_get_current_data_ex(Z_ARRVAL_P(arg1), &pos1)) != NULL + && (data2 = zend_hash_get_current_data_ex(Z_ARRVAL_P(arg2), &pos2)) != NULL) { + + dx = Z_DVAL_P(data1) - mx; + dy = Z_DVAL_P(data2) - my; + + vx += dx * dx; + vy += dy * dy; + cc += dx * dy; + + zend_hash_move_forward_ex(Z_ARRVAL_P(arg1), &pos1); + zend_hash_move_forward_ex(Z_ARRVAL_P(arg2), &pos2); + } + + if (vx == 0.0 || vy == 0.0) { + php_error_docref(NULL, E_WARNING, "Correlation is undefined when one or both arrays have zero variance"); + RETURN_FALSE; + } + rr = cc / sqrt(vx * vy); + if (rr > 1.0) rr = 1.0; + if (rr < -1.0) rr = -1.0; + RETURN_DOUBLE(rr); } /* }}} */ diff --git a/tests/stats_stat_correlation_degenerate.phpt b/tests/stats_stat_correlation_degenerate.phpt new file mode 100644 index 0000000..f51d506 --- /dev/null +++ b/tests/stats_stat_correlation_degenerate.phpt @@ -0,0 +1,28 @@ +--TEST-- +stats_stat_correlation() degenerate inputs +--FILE-- +<?php +// empty arrays +var_dump(stats_stat_correlation([], [])); + +// single-element arrays +var_dump(stats_stat_correlation([1], [2])); + +// zero variance in X +var_dump(stats_stat_correlation([1, 1, 1], [1, 2, 3])); + +// zero variance in Y +var_dump(stats_stat_correlation([1, 2, 3], [5, 5, 5])); +?> +--EXPECTF-- +Warning: stats_stat_correlation(): Correlation requires at least 2 data points in %s on line %d +bool(false) + +Warning: stats_stat_correlation(): Correlation requires at least 2 data points in %s on line %d +bool(false) + +Warning: stats_stat_correlation(): Correlation is undefined when one or both arrays have zero variance in %s on line %d +bool(false) + +Warning: stats_stat_correlation(): Correlation is undefined when one or both arrays have zero variance in %s on line %d +bool(false)

« previous php.pecl.cvs (#28545) next »