[pecl-math-stats] master: Merge pull request #16 from php/fix/stats_stat_correlation

From: Date: Tue, 21 Apr 2026 03:27:36 +0000
Subject: [pecl-math-stats] master: Merge pull request #16 from php/fix/stats_stat_correlation
Groups: php.pecl.cvs 
Request: Send a blank email to pecl-cvs+get-28548@lists.php.net to get a copy of this message
Author: Rasmus Lerdorf (rlerdorf) Committer: GitHub (web-flow) Pusher: rlerdorf Date: 2026-04-21T04:27:34+01:00 Commit: https://github.com/php/pecl-math-stats/commit/d81618d3eeb3d29bcd3c02ac29678baac2739484 Raw diff: https://github.com/php/pecl-math-stats/commit/d81618d3eeb3d29bcd3c02ac29678baac2739484.diff Merge pull request #16 from php/fix/stats_stat_correlation Fix stats_stat_correlation() returning near-zero residuals on ARM64 Changed paths: A tests/stats_stat_correlation_degenerate.phpt M package.xml M php_stats.c Diff: diff --git a/package.xml b/package.xml index 65e50cb..56f93ef 100644 --- a/package.xml +++ b/package.xml @@ -125,6 +125,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> <file name="stats_standard_deviation.phpt" role="test" /> <file name="stats_stat_binomial_coef.phpt" role="test" /> <file name="stats_stat_correlation.phpt" role="test" /> + <file name="stats_stat_correlation_degenerate.phpt" role="test" /> <file name="stats_stat_factorial.phpt" role="test" /> <file name="stats_stat_independent_t.phpt" role="test" /> <file name="stats_stat_innerproduct.phpt" role="test" /> diff --git a/php_stats.c b/php_stats.c index 5846efb..88f101b 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,38 @@ 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) { + + convert_to_double_ex(data1); + convert_to_double_ex(data2); + + 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..493d0ab --- /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(array(), array())); + +// single-element arrays +var_dump(stats_stat_correlation(array(1), array(2))); + +// zero variance in X +var_dump(stats_stat_correlation(array(1, 1, 1), array(1, 2, 3))); + +// zero variance in Y +var_dump(stats_stat_correlation(array(1, 2, 3), array(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 (#28548) next »