Skip to content

Commit a18abb3

Browse files
committed
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.
1 parent 8799a04 commit a18abb3

File tree

4 files changed

+31
-61
lines changed

4 files changed

+31
-61
lines changed

src/PhpSpreadsheet/Calculation/MathTrig/Round.php

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
66
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
77
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
8+
// following added in Php8.4
9+
use RoundingMode;
810

911
class Round
1012
{
@@ -67,31 +69,28 @@ public static function up($number, $digits): array|string|float
6769
return 0.0;
6870
}
6971

70-
$digitsPlus1 = $digits + 1;
71-
if ($number < 0.0) {
72-
if ($digitsPlus1 < 0) {
73-
return round($number - 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_DOWN);
74-
}
75-
$result = sprintf("%.{$digitsPlus1}F", $number - 0.5 * 0.1 ** $digits);
76-
77-
return round((float) $result, $digits, PHP_ROUND_HALF_DOWN);
72+
if (PHP_VERSION_ID >= 80400) {
73+
return round(
74+
(float) (string) $number,
75+
$digits,
76+
RoundingMode::AwayFromZero //* @phpstan-ignore-line
77+
);
7878
}
7979

80-
if ($digitsPlus1 < 0) {
81-
return round($number + 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_DOWN);
80+
if ($number < 0.0) {
81+
return round($number - 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_DOWN);
8282
}
83-
$result = sprintf("%.{$digitsPlus1}F", $number + 0.5 * 0.1 ** $digits);
8483

85-
return round((float) $result, $digits, PHP_ROUND_HALF_DOWN);
84+
return round($number + 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_DOWN);
8685
}
8786

8887
/**
8988
* ROUNDDOWN.
9089
*
9190
* Rounds a number down to a specified number of decimal places
9291
*
93-
* @param array|float $number Number to round, or can be an array of numbers
94-
* @param array|int $digits Number of digits to which you want to round $number, or can be an array of numbers
92+
* @param null|array|float|string $number Number to round, or can be an array of numbers
93+
* @param array|float|int|string $digits Number of digits to which you want to round $number, or can be an array of numbers
9594
*
9695
* @return array|float|string Rounded Number, or a string containing an error
9796
* If an array of numbers is passed as the argument, then the returned result will also be an array
@@ -114,23 +113,19 @@ public static function down($number, $digits): array|string|float
114113
return 0.0;
115114
}
116115

117-
$digitsPlus1 = $digits + 1;
118-
if ($number < 0.0) {
119-
if ($digitsPlus1 < 0) {
120-
return round($number + 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_UP);
121-
}
122-
$result = sprintf("%.{$digitsPlus1}F", $number + 0.5 * 0.1 ** $digits);
123-
124-
return round((float) $result, $digits, PHP_ROUND_HALF_UP);
116+
if (PHP_VERSION_ID >= 80400) {
117+
return round(
118+
(float) (string) $number,
119+
$digits,
120+
RoundingMode::TowardsZero //* @phpstan-ignore-line
121+
);
125122
}
126123

127-
if ($digitsPlus1 < 0) {
128-
return round($number - 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_UP);
124+
if ($number < 0.0) {
125+
return round($number + 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_UP);
129126
}
130127

131-
$result = sprintf("%.{$digitsPlus1}F", $number - 0.5 * 0.1 ** $digits);
132-
133-
return round((float) $result, $digits, PHP_ROUND_HALF_UP);
128+
return round($number - 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_UP);
134129
}
135130

136131
/**

src/PhpSpreadsheet/Calculation/MathTrig/Trunc.php

Lines changed: 4 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
44

55
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
6-
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
76

87
class Trunc
98
{
@@ -19,47 +18,19 @@ class Trunc
1918
* (or possibly that value minus 1).
2019
* Excel is unlikely to do any better.
2120
*
22-
* @param array|float $value Or can be an array of values
23-
* @param array|int $digits Or can be an array of values
21+
* @param null|array|float|string $value Or can be an array of values
22+
* @param array|float|int|string $digits Or can be an array of values
2423
*
2524
* @return array|float|string Truncated value, or a string containing an error
2625
* If an array of numbers is passed as an argument, then the returned result will also be an array
2726
* with the same dimensions
2827
*/
29-
public static function evaluate(array|float|string|null $value = 0, array|int|string $digits = 0): array|float|string
28+
public static function evaluate(array|float|string|null $value = 0, array|float|int|string $digits = 0): array|float|string
3029
{
3130
if (is_array($value) || is_array($digits)) {
3231
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $digits);
3332
}
3433

35-
try {
36-
$value = Helpers::validateNumericNullBool($value);
37-
$digits = Helpers::validateNumericNullSubstitution($digits, null);
38-
} catch (Exception $e) {
39-
return $e->getMessage();
40-
}
41-
42-
if ($value == 0) {
43-
return $value;
44-
}
45-
46-
if ($value >= 0) {
47-
$minusSign = '';
48-
} else {
49-
$minusSign = '-';
50-
$value = -$value;
51-
}
52-
$digits = (int) floor($digits);
53-
if ($digits < 0) {
54-
$result = (float) (substr(sprintf('%.0F', $value), 0, $digits) . str_repeat('0', -$digits));
55-
56-
return ($minusSign === '') ? $result : -$result;
57-
}
58-
$decimals = (floor($value) == (int) $value) ? (PHP_FLOAT_DIG - strlen((string) (int) $value)) : $digits;
59-
$resultString = ($decimals < 0) ? sprintf('%F', $value) : sprintf('%.' . $decimals . 'F', $value);
60-
$regExp = '/([.]\\d{' . $digits . '})\\d+$/';
61-
$result = $minusSign . (preg_replace($regExp, '$1', $resultString) ?? $resultString);
62-
63-
return (float) $result;
34+
return Round::down($value, $digits);
6435
}
6536
}

tests/data/Calculation/MathTrig/ROUNDDOWN.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,7 @@
3333
[0, 'B1, 0'],
3434
['exception', ''],
3535
['exception', '35.51'],
36-
'negative number and precision' => [-31400, '-31415.92654, -2'],
36+
'negative number and precision ROUNDDOWN' => [-31400, '-31415.92654, -2'],
37+
'issue 4213 #1' => [249.0, '249.9975, 0'],
38+
'issue 4213 #2' => [-249.0, '-249.9975, 0'],
3739
];

tests/data/Calculation/MathTrig/ROUNDUP.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,6 @@
3434
['exception', ''],
3535
['exception', '35.51'],
3636
'negative number and precision' => [-31500, '-31415.92654, -2'],
37+
'issue 4213 #1' => [250.0, '249.9975, 0'],
38+
'issue 4213 #2' => [-250.0, '-249.9975, 0'],
3739
];

0 commit comments

Comments
 (0)