Changes to ROUNDDOWN/ROUNDUP/TRUNC

Fix #4213. Early versions of Php 8.4 caused problems for some Excel functions, among them ROUNDDOWN and ROUNDUP. New code was added which seemed to work for 8.4 and all prior releases (back to 7.4). However, as the issue shows, there is a problem with the new logic that had not been a problem for prior releases (PhpSpreadsheet 1.29.1 with Php 8.3 or earlier).

As it happens, some time after 8.4 broke the existing functionality, new features were added to it which enabled much cleaner logic. The code will now take this cleaner path when available, and revert to the older logic when not. This appears to work for all relevant Php releases, at least till the next non-match between PhpSpreadsheet and Excel is reported.

And, having made the change to ROUNDDOWN, it occurred to me that ROUNDDOWN always returns the same result as TRUNC (they differ only in number of required arguments). So I changed TRUNC to call ROUNDDOWN, and that didn't break anything. So I will keep that change. This probably means I should change Calculation so that ROUNDDOWN is called automatically, and deprecate TRUNC. That can happen at a later time.
This commit is contained in:
oleibman
2024-11-04 19:23:53 -08:00
parent 8799a041a0
commit a18abb3e0f
4 changed files with 33 additions and 63 deletions
@@ -5,6 +5,8 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
// following added in Php8.4
use RoundingMode;
class Round
{
@@ -67,22 +69,19 @@ class Round
return 0.0;
}
$digitsPlus1 = $digits + 1;
if (PHP_VERSION_ID >= 80400) {
return round(
(float) (string) $number,
$digits,
RoundingMode::AwayFromZero //* @phpstan-ignore-line
);
}
if ($number < 0.0) {
if ($digitsPlus1 < 0) {
return round($number - 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_DOWN);
}
$result = sprintf("%.{$digitsPlus1}F", $number - 0.5 * 0.1 ** $digits);
return round((float) $result, $digits, PHP_ROUND_HALF_DOWN);
return round($number - 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_DOWN);
}
if ($digitsPlus1 < 0) {
return round($number + 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_DOWN);
}
$result = sprintf("%.{$digitsPlus1}F", $number + 0.5 * 0.1 ** $digits);
return round((float) $result, $digits, PHP_ROUND_HALF_DOWN);
return round($number + 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_DOWN);
}
/**
@@ -90,8 +89,8 @@ class Round
*
* Rounds a number down to a specified number of decimal places
*
* @param array|float $number Number to round, or can be an array of numbers
* @param array|int $digits Number of digits to which you want to round $number, or can be an array of numbers
* @param null|array|float|string $number Number to round, or can be an array of numbers
* @param array|float|int|string $digits Number of digits to which you want to round $number, or can be an array of numbers
*
* @return array|float|string Rounded Number, or a string containing an error
* If an array of numbers is passed as the argument, then the returned result will also be an array
@@ -114,23 +113,19 @@ class Round
return 0.0;
}
$digitsPlus1 = $digits + 1;
if (PHP_VERSION_ID >= 80400) {
return round(
(float) (string) $number,
$digits,
RoundingMode::TowardsZero //* @phpstan-ignore-line
);
}
if ($number < 0.0) {
if ($digitsPlus1 < 0) {
return round($number + 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_UP);
}
$result = sprintf("%.{$digitsPlus1}F", $number + 0.5 * 0.1 ** $digits);
return round((float) $result, $digits, PHP_ROUND_HALF_UP);
return round($number + 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_UP);
}
if ($digitsPlus1 < 0) {
return round($number - 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_UP);
}
$result = sprintf("%.{$digitsPlus1}F", $number - 0.5 * 0.1 ** $digits);
return round((float) $result, $digits, PHP_ROUND_HALF_UP);
return round($number - 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_UP);
}
/**
@@ -3,7 +3,6 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
class Trunc
{
@@ -19,47 +18,19 @@ class Trunc
* (or possibly that value minus 1).
* Excel is unlikely to do any better.
*
* @param array|float $value Or can be an array of values
* @param array|int $digits Or can be an array of values
* @param null|array|float|string $value Or can be an array of values
* @param array|float|int|string $digits Or can be an array of values
*
* @return array|float|string Truncated value, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function evaluate(array|float|string|null $value = 0, array|int|string $digits = 0): array|float|string
public static function evaluate(array|float|string|null $value = 0, array|float|int|string $digits = 0): array|float|string
{
if (is_array($value) || is_array($digits)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $digits);
}
try {
$value = Helpers::validateNumericNullBool($value);
$digits = Helpers::validateNumericNullSubstitution($digits, null);
} catch (Exception $e) {
return $e->getMessage();
}
if ($value == 0) {
return $value;
}
if ($value >= 0) {
$minusSign = '';
} else {
$minusSign = '-';
$value = -$value;
}
$digits = (int) floor($digits);
if ($digits < 0) {
$result = (float) (substr(sprintf('%.0F', $value), 0, $digits) . str_repeat('0', -$digits));
return ($minusSign === '') ? $result : -$result;
}
$decimals = (floor($value) == (int) $value) ? (PHP_FLOAT_DIG - strlen((string) (int) $value)) : $digits;
$resultString = ($decimals < 0) ? sprintf('%F', $value) : sprintf('%.' . $decimals . 'F', $value);
$regExp = '/([.]\\d{' . $digits . '})\\d+$/';
$result = $minusSign . (preg_replace($regExp, '$1', $resultString) ?? $resultString);
return (float) $result;
return Round::down($value, $digits);
}
}
@@ -33,5 +33,7 @@ return [
[0, 'B1, 0'],
['exception', ''],
['exception', '35.51'],
'negative number and precision' => [-31400, '-31415.92654, -2'],
'negative number and precision ROUNDDOWN' => [-31400, '-31415.92654, -2'],
'issue 4213 #1' => [249.0, '249.9975, 0'],
'issue 4213 #2' => [-249.0, '-249.9975, 0'],
];
@@ -34,4 +34,6 @@ return [
['exception', ''],
['exception', '35.51'],
'negative number and precision' => [-31500, '-31415.92654, -2'],
'issue 4213 #1' => [250.0, '249.9975, 0'],
'issue 4213 #2' => [-250.0, '-249.9975, 0'],
];