random: Randomizer::getFloat(): Fix check for empty open intervals (#10185)

* random: Randomizer::getFloat(): Fix check for empty open intervals

The check for invalid parameters for the IntervalBoundary::OpenOpen variant was
not correct: If two consecutive doubles are passed as parameters, the resulting
interval is empty, resulting in an uint64 underflow in the γ-section
implementation.

Instead of checking whether `$min < $max`, we must check that there is at least
one more double between `$min` and `$max`, i.e. it must hold that:

	nextafter($min, $max) != $max

Instead of duplicating the comparatively complicated and expensive `nextafter`
logic for a rare error case we instead return `NAN` from the γ-section
implementation when the parameters result in an empty interval and thus underflow.

This allows us to reliably detect this specific error case *after* the fact,
but without modifying the engine state. It also provides reliable error
reporting for other internal functions that might use the γ-section
implementation.

* random: γ-section: Also check that that min is smaller than max

This extends the empty-interval check in the γ-section implementation with a
check that min is actually the smaller of the two parameters.

* random: Use PHP_FLOAT_EPSILON in getFloat_error.phpt

Co-authored-by: Christoph M. Becker <cmbecker69@gmx.de>
This commit is contained in:
Tim Düsterhus 2023-01-10 10:16:33 +01:00 committed by GitHub
parent 4280431050
commit 13b82eef84
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 40 additions and 2 deletions

View file

@ -190,7 +190,14 @@ PHP_METHOD(Random_Randomizer, getFloat)
RETURN_THROWS();
}
RETURN_DOUBLE(php_random_gammasection_open_open(randomizer->algo, randomizer->status, min, max));
RETVAL_DOUBLE(php_random_gammasection_open_open(randomizer->algo, randomizer->status, min, max));
if (UNEXPECTED(isnan(Z_DVAL_P(return_value)))) {
zend_value_error("The given interval is empty, there are no floats between argument #1 ($min) and argument #2 ($max).");
RETURN_THROWS();
}
return;
default:
ZEND_UNREACHABLE();
}