Skip to content

Commit ab420f4

Browse files
authored
Coerce Bool to Int for Mathematical Operators on Arrays (#3392)
* Coerce Bool to Int for Unary Operation on Arrays Fix #3389. It seems some functionality was left behind when JAMA was eliminated (PR #3260). In particular, it is apparently a known trick to use double negation on boolean values as arguments to functions like SUMPRODUCT. * Fix 3396 Treat booleans in arrays as int for mathematical operators as well. * Edge Case When array operand was neither numeric nor boolean, PhpSpreadsheet had always been evaluating the operand as #NUM!. It will now propagate an error string like #DIV/0!, and treat non-error strings as #VALUE!, consistent with Excel.
1 parent 6925b7f commit ab420f4

File tree

4 files changed

+104
-6
lines changed

4 files changed

+104
-6
lines changed

src/PhpSpreadsheet/Calculation/Calculation.php

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4944,10 +4944,10 @@ private function processTokenStack($tokens, $cellID = null, ?Cell $cell = null)
49444944
[$rows, $columns] = self::checkMatrixOperands($result, $operand2, 0);
49454945
for ($row = 0; $row < $rows; ++$row) {
49464946
for ($column = 0; $column < $columns; ++$column) {
4947-
if (is_numeric($result[$row][$column])) {
4947+
if (self::isNumericOrBool($result[$row][$column])) {
49484948
$result[$row][$column] *= $multiplier;
49494949
} else {
4950-
$result[$row][$column] = Information\ExcelError::VALUE();
4950+
$result[$row][$column] = self::makeError($result[$row][$column]);
49514951
}
49524952
}
49534953
}
@@ -5336,15 +5336,15 @@ private function executeNumericBinaryOperation($operand1, $operand2, $operation,
53365336
for ($column = 0; $column < $columns; ++$column) {
53375337
if ($operand1[$row][$column] === null) {
53385338
$operand1[$row][$column] = 0;
5339-
} elseif (!is_numeric($operand1[$row][$column])) {
5340-
$operand1[$row][$column] = Information\ExcelError::VALUE();
5339+
} elseif (!self::isNumericOrBool($operand1[$row][$column])) {
5340+
$operand1[$row][$column] = self::makeError($operand1[$row][$column]);
53415341

53425342
continue;
53435343
}
53445344
if ($operand2[$row][$column] === null) {
53455345
$operand2[$row][$column] = 0;
5346-
} elseif (!is_numeric($operand2[$row][$column])) {
5347-
$operand1[$row][$column] = Information\ExcelError::VALUE();
5346+
} elseif (!self::isNumericOrBool($operand2[$row][$column])) {
5347+
$operand1[$row][$column] = self::makeError($operand2[$row][$column]);
53485348

53495349
continue;
53505350
}
@@ -5764,4 +5764,16 @@ private static function boolToString($operand1)
57645764

57655765
return $operand1;
57665766
}
5767+
5768+
/** @param mixed $operand */
5769+
private static function isNumericOrBool($operand): bool
5770+
{
5771+
return is_numeric($operand) || is_bool($operand);
5772+
}
5773+
5774+
/** @param mixed $operand */
5775+
private static function makeError($operand = ''): string
5776+
{
5777+
return Information\ErrorValue::isError($operand) ? $operand : Information\ExcelError::VALUE();
5778+
}
57675779
}

tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndexTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
66
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Matrix;
7+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
78
use PHPUnit\Framework\TestCase;
89

910
class IndexTest extends TestCase
@@ -68,4 +69,35 @@ public function providerIndexArray(): array
6869
],
6970
];
7071
}
72+
73+
public function testPropagateDiv0(): void
74+
{
75+
// issue 3396 error was always being treated as #VALUE!
76+
$spreadsheet = new Spreadsheet();
77+
$sheet = $spreadsheet->getActiveSheet();
78+
$sheet->getCell('A1')->setValue(0);
79+
$sheet->getCell('A3')->setValue(1);
80+
$sheet->getCell('B3')->setValue(1);
81+
$sheet->getCell('C3')->setValue('=1/A1');
82+
$sheet->getCell('D3')->setValue('=1/A1');
83+
$sheet->getCell('E3')->setValue('xyz');
84+
$sheet->getCell('A4')->setValue(false);
85+
$sheet->getCell('B4')->setValue(true);
86+
$sheet->getCell('C4')->setValue(true);
87+
$sheet->getCell('D4')->setValue(false);
88+
$sheet->getCell('E4')->setValue(false);
89+
$sheet->getCell('A6')->setValue('=INDEX(A3:E3/A4:E4,1,1)');
90+
$sheet->getCell('B6')->setValue('=INDEX(A3:E3/A4:E4,1,2)');
91+
$sheet->getCell('C6')->setValue('=INDEX(A3:E3/A4:E4,1,3)');
92+
$sheet->getCell('D6')->setValue('=INDEX(A3:E3/A4:E4,1,4)');
93+
$sheet->getCell('E6')->setValue('=INDEX(A3:E3/A4:E4,1,5)');
94+
95+
self::assertSame('#DIV/0!', $sheet->getCell('A6')->getCalculatedValue());
96+
self::assertSame(1, $sheet->getCell('B6')->getCalculatedValue());
97+
self::assertSame('#DIV/0!', $sheet->getCell('C6')->getCalculatedValue());
98+
self::assertSame('#DIV/0!', $sheet->getCell('D6')->getCalculatedValue());
99+
self::assertSame('#VALUE!', $sheet->getCell('E6')->getCalculatedValue());
100+
101+
$spreadsheet->disconnectWorksheets();
102+
}
71103
}

tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/LookupTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
66
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
7+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
78
use PHPUnit\Framework\TestCase;
89

910
class LookupTest extends TestCase
@@ -47,4 +48,33 @@ public function providerLookupArray(): array
4748
],
4849
];
4950
}
51+
52+
public function testBoolsAsInt(): void
53+
{
54+
// issue 3396 not handling math operation for bool in array
55+
$spreadsheet = new Spreadsheet();
56+
$sheet = $spreadsheet->getActiveSheet();
57+
$sheet->getCell('A59')->setValue('start');
58+
$sheet->getCell('B59')->setValue('end');
59+
$sheet->getCell('C59')->setValue('percent');
60+
$sheet->getCell('A60')->setValue('=DATEVALUE("1950-01-01")');
61+
$sheet->getCell('B60')->setValue('=DATEVALUE("2016-06-03")');
62+
$sheet->getCell('C60')->setValue(0.05);
63+
$sheet->getCell('A61')->setValue('=DATEVALUE("2016-06-04")');
64+
$sheet->getCell('B61')->setValue('=DATEVALUE("2021-01-05")');
65+
$sheet->getCell('C61')->setValue(0.08);
66+
$sheet->getCell('A62')->setValue('=DATEVALUE("2021-01-16")');
67+
$sheet->getCell('B62')->setValue('=DATEVALUE("2022-04-08")');
68+
$sheet->getCell('C62')->setValue(0.03);
69+
$sheet->getCell('A63')->setValue('=DATEVALUE("2022-04-09")');
70+
$sheet->getCell('B63')->setValue('=DATEVALUE("2500-12-31")');
71+
$sheet->getCell('C63')->setValue(0.04);
72+
73+
$sheet->getCell('D5')->setValue(5);
74+
$sheet->getCell('E5')->setValue('=DATEVALUE("2023-01-01")');
75+
$sheet->getCell('D7')->setValue('=IF(E5<>"",LOOKUP(2,1/($A$60:$A$63<=E5)/($B$60:$B$63>=E5),$C$60:$C$63)*D5,"")');
76+
77+
self::assertSame(0.2, $sheet->getCell('D7')->getCalculatedValue());
78+
$spreadsheet->disconnectWorksheets();
79+
}
5080
}

tests/PhpSpreadsheetTests/Calculation/Functions/MathTrig/SumProductTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,28 @@ public function providerSUMPRODUCT(): array
3838
{
3939
return require 'tests/data/Calculation/MathTrig/SUMPRODUCT.php';
4040
}
41+
42+
public function testBoolsAsInt(): void
43+
{
44+
// issue 3389 not handling unary minus with boolean value
45+
$sheet = $this->getSheet();
46+
$sheet->fromArray(
47+
[
48+
['Range 1', 'Range 2', null, 'Blue matches', 'Red matches'],
49+
[0, 'Red', null, '=SUMPRODUCT(--(B3:B10=1), --(C3:C10="BLUE"))', '=SUMPRODUCT(--(B3:B10=1), --(C3:C10="RED"))'],
50+
[1, 'Blue'],
51+
[0, 'Blue'],
52+
[1, 'Red'],
53+
[1, 'Blue'],
54+
[0, 'Blue'],
55+
[1, 'Red'],
56+
[1, 'Blue'],
57+
],
58+
null, // null value
59+
'B2', // start cell
60+
true // strict null comparison
61+
);
62+
self::assertSame(3, $sheet->getCell('E3')->getCalculatedValue());
63+
self::assertSame(2, $sheet->getCell('F3')->getCalculatedValue());
64+
}
4165
}

0 commit comments

Comments
 (0)