php-src/ext/random/tests/03_randomizer/methods/getFloat_error.phpt
Tim Düsterhus 13b82eef84
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>
2023-01-10 10:16:33 +01:00

130 lines
4.7 KiB
PHP

--TEST--
Random: Randomizer: getFloat(): Parameters are correctly validated
--FILE--
<?php
use Random\IntervalBoundary;
use Random\Randomizer;
function randomizer(): Randomizer
{
return new Randomizer();
}
foreach ([
IntervalBoundary::ClosedClosed,
IntervalBoundary::ClosedOpen,
IntervalBoundary::OpenClosed,
IntervalBoundary::OpenOpen,
] as $boundary) {
echo $boundary->name, PHP_EOL;
try {
var_dump(randomizer()->getFloat(NAN, 0.0, $boundary));
} catch (ValueError $e) {
echo $e->getMessage(), PHP_EOL;
}
try {
var_dump(randomizer()->getFloat(INF, 0.0, $boundary));
} catch (ValueError $e) {
echo $e->getMessage(), PHP_EOL;
}
try {
var_dump(randomizer()->getFloat(-INF, 0.0, $boundary));
} catch (ValueError $e) {
echo $e->getMessage(), PHP_EOL;
}
try {
var_dump(randomizer()->getFloat(0.0, NAN, $boundary));
} catch (ValueError $e) {
echo $e->getMessage(), PHP_EOL;
}
try {
var_dump(randomizer()->getFloat(0.0, INF, $boundary));
} catch (ValueError $e) {
echo $e->getMessage(), PHP_EOL;
}
try {
var_dump(randomizer()->getFloat(0.0, -INF, $boundary));
} catch (ValueError $e) {
echo $e->getMessage(), PHP_EOL;
}
try {
var_dump(randomizer()->getFloat(0.0, -0.1, $boundary));
} catch (ValueError $e) {
echo $e->getMessage(), PHP_EOL;
}
try {
var_dump(randomizer()->getFloat(0.0, 0.0, $boundary));
} catch (ValueError $e) {
echo $e->getMessage(), PHP_EOL;
}
try {
// Both values round to the same float.
var_dump(randomizer()->getFloat(100_000_000_000_000_000.0, 100_000_000_000_000_000.1, $boundary));
} catch (ValueError $e) {
echo $e->getMessage(), PHP_EOL;
}
try {
// There is no float between the two parameters, thus making the OpenOpen interval empty.
var_dump(randomizer()->getFloat(1.0, 1 + PHP_FLOAT_EPSILON, $boundary));
} catch (ValueError $e) {
echo $e->getMessage(), PHP_EOL;
}
}
?>
--EXPECTF--
ClosedClosed
Random\Randomizer::getFloat(): Argument #1 ($min) must be finite
Random\Randomizer::getFloat(): Argument #1 ($min) must be finite
Random\Randomizer::getFloat(): Argument #1 ($min) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be greater than or equal to argument #1 ($min)
float(0)
float(1.0E+17)
float(%f)
ClosedOpen
Random\Randomizer::getFloat(): Argument #1 ($min) must be finite
Random\Randomizer::getFloat(): Argument #1 ($min) must be finite
Random\Randomizer::getFloat(): Argument #1 ($min) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be greater than argument #1 ($min)
Random\Randomizer::getFloat(): Argument #2 ($max) must be greater than argument #1 ($min)
Random\Randomizer::getFloat(): Argument #2 ($max) must be greater than argument #1 ($min)
float(1)
OpenClosed
Random\Randomizer::getFloat(): Argument #1 ($min) must be finite
Random\Randomizer::getFloat(): Argument #1 ($min) must be finite
Random\Randomizer::getFloat(): Argument #1 ($min) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be greater than argument #1 ($min)
Random\Randomizer::getFloat(): Argument #2 ($max) must be greater than argument #1 ($min)
Random\Randomizer::getFloat(): Argument #2 ($max) must be greater than argument #1 ($min)
float(1.0000000000000002)
OpenOpen
Random\Randomizer::getFloat(): Argument #1 ($min) must be finite
Random\Randomizer::getFloat(): Argument #1 ($min) must be finite
Random\Randomizer::getFloat(): Argument #1 ($min) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be finite
Random\Randomizer::getFloat(): Argument #2 ($max) must be greater than argument #1 ($min)
Random\Randomizer::getFloat(): Argument #2 ($max) must be greater than argument #1 ($min)
Random\Randomizer::getFloat(): Argument #2 ($max) must be greater than argument #1 ($min)
The given interval is empty, there are no floats between argument #1 ($min) and argument #2 ($max).