Skip to content

Commit f553019

Browse files
committed
New Algorithm for TRUNC, ROUNDUP, and ROUNDDOWN
Fix #4113. TRUNC isn't always producing the expected result. There was a promising algorithm at https://stackoverflow.com/questions/4668628/truncate-float-numbers-with-php from user Juan. It works through Php8.3, but failed in Php8.4 (more on this later). User Savageman on the same page has a solution that needs work, but, once the work had taken place, it works on Php8.1-8.4. The ROUNDUP and ROUNDDOWN functions were adversely affected by Php8.4, probably for the same reasons as Juan's TRUNC suggestion. I put a kludge in place for them some time ago, but I wasn't happy with it. The solution used for TRUNC here suggested a change to the ROUNDUP and ROUNDDOWN code that would no longer require the kludge. The change to those functions now works more cleanly on Php8.1-8.4.
1 parent b406367 commit f553019

File tree

5 files changed

+55
-12
lines changed

5 files changed

+55
-12
lines changed

src/PhpSpreadsheet/Calculation/MathTrig/Round.php

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ class Round
1010
{
1111
use ArrayEnabled;
1212

13-
private const ROUNDING_ADJUSTMENT = (PHP_VERSION_ID < 80400) ? 0 : 1e-14;
14-
1513
/**
1614
* ROUND.
1715
*
@@ -69,11 +67,22 @@ public static function up($number, $digits): array|string|float
6967
return 0.0;
7068
}
7169

70+
$digitsPlus1 = $digits + 1;
7271
if ($number < 0.0) {
73-
return round($number - 0.5 * 0.1 ** $digits + self::ROUNDING_ADJUSTMENT, $digits, PHP_ROUND_HALF_DOWN);
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);
78+
}
79+
80+
if ($digitsPlus1 < 0) {
81+
return round($number + 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_DOWN);
7482
}
83+
$result = sprintf("%.{$digitsPlus1}f", $number + 0.5 * 0.1 ** $digits);
7584

76-
return round($number + 0.5 * 0.1 ** $digits - self::ROUNDING_ADJUSTMENT, $digits, PHP_ROUND_HALF_DOWN);
85+
return round((float) $result, $digits, PHP_ROUND_HALF_DOWN);
7786
}
7887

7988
/**
@@ -105,11 +114,23 @@ public static function down($number, $digits): array|string|float
105114
return 0.0;
106115
}
107116

117+
$digitsPlus1 = $digits + 1;
108118
if ($number < 0.0) {
109-
return round($number + 0.5 * 0.1 ** $digits - self::ROUNDING_ADJUSTMENT, $digits, PHP_ROUND_HALF_UP);
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);
125+
}
126+
127+
if ($digitsPlus1 < 0) {
128+
return round($number - 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_UP);
110129
}
111130

112-
return round($number - 0.5 * 0.1 ** $digits + self::ROUNDING_ADJUSTMENT, $digits, PHP_ROUND_HALF_UP);
131+
$result = sprintf("%.{$digitsPlus1}f", $number - 0.5 * 0.1 ** $digits);
132+
133+
return round((float) $result, $digits, PHP_ROUND_HALF_UP);
113134
}
114135

115136
/**

src/PhpSpreadsheet/Calculation/MathTrig/Trunc.php

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,26 @@ public static function evaluate(array|float|string|null $value = 0, array|int|st
3434
return $e->getMessage();
3535
}
3636

37-
$digits = floor($digits);
37+
if ($value == 0) {
38+
return $value;
39+
}
3840

39-
// Truncate
40-
$adjust = 10 ** $digits;
41+
if ($value >= 0) {
42+
$minusSign = '';
43+
} else {
44+
$minusSign = '-';
45+
$value = -$value;
46+
}
47+
$digits = (int) floor($digits);
48+
if ($digits < 0) {
49+
$power = (int) (10 ** -$digits);
50+
$result = intdiv((int) floor($value), $power) * $power;
4151

42-
if (($digits > 0) && (rtrim((string) (int) ((abs($value) - abs((int) $value)) * $adjust), '0') < $adjust / 10)) {
43-
return $value;
52+
return ($minusSign === '') ? $result : -$result;
4453
}
54+
$digitsPlus1 = $digits + 1;
55+
$result = substr($minusSign . sprintf("%.{$digitsPlus1}f", $value), 0, -1);
4556

46-
return ((int) ($value * $adjust)) / $adjust;
57+
return (float) $result;
4758
}
4859
}

tests/data/Calculation/MathTrig/ROUNDDOWN.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,5 @@
3333
[0, 'B1, 0'],
3434
['exception', ''],
3535
['exception', '35.51'],
36+
'negative number and precision' => [-31400, '-31415.92654, -2'],
3637
];

tests/data/Calculation/MathTrig/ROUNDUP.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,5 @@
3333
[0, 'B1, 0'],
3434
['exception', ''],
3535
['exception', '35.51'],
36+
'negative number and precision' => [-31500, '-31415.92654, -2'],
3637
];

tests/data/Calculation/MathTrig/TRUNC.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
[-31415.92654, '-31415.92654, 10'],
1212
[31415.92, '31415.92654, 2'],
1313
[31400, '31415.92654, -2'],
14+
'negative number and precision' => [-31400, '-31415.92654, -2'],
1415
[0, '31415.92654, -10'],
1516
[0, '-31415.92654, -10'],
1617
[12000, '12345.6789, -3'],
@@ -32,4 +33,12 @@
3233
[-3, 'A4'],
3334
[-5, 'A5'],
3435
[0, 'B1'],
36+
'issue4113' => [1.0, '1.01, 1'],
37+
'issue4113 negative' => [-1.0, '-1.01, 1'],
38+
'issue4113 additional' => [10.04, '10.04, 2'],
39+
'issue4113 additional negative' => [-10.04, '-10.04, 2'],
40+
'issue4113 small fraction keep all' => [0.04, '0.04, 2'],
41+
'issue4113 small negative fraction keep all' => [-0.04, '-0.04, 2'],
42+
'issue4113 small fraction lose some' => [0.0, '0.01, 1'],
43+
'issue4113 small negative fraction lose some' => [0.0, '-0.001, 1'],
3544
];

0 commit comments

Comments
 (0)