Skip to content

Commit 996de17

Browse files
authored
Merge pull request #4640 from oleibman/lessprecise
Option To Display Numbers With Less Precision
2 parents 5179fff + f8fc6b5 commit 996de17

File tree

8 files changed

+106
-26
lines changed

8 files changed

+106
-26
lines changed

.php-cs-fixer.dist.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@
166166
'php_unit_test_class_requires_covers' => false, // We don't care as much as we should about coverage
167167
'phpdoc_add_missing_param_annotation' => false, // Don't add things that bring no value
168168
'phpdoc_align' => false, // Waste of time
169-
'phpdoc_annotation_without_dot' => true,
169+
'phpdoc_annotation_without_dot' => false,
170170
'phpdoc_indent' => true,
171171
'phpdoc_line_span' => false, // Unfortunately our old comments turn even uglier with this
172172
'phpdoc_no_access' => true,

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). Thia is a
99

1010
### Added
1111

12-
- Nothing yet.
12+
- Option to display numbers with less precision. [Issue #4626](https://github.com/PHPOffice/PhpSpreadsheet/issues/4626) [PR #4640](https://github.com/PHPOffice/PhpSpreadsheet/pull/4640)
1313

1414
### Removed
1515

docs/topics/Looping the Loop.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ It can return the raw cell value (which isn't particularly useful if the cell co
5757
| $calculateFormulas | boolean | false | Flag to indicate if formula values should be calculated before returning. |
5858
| $formatData | boolean | false | Flag to request that values should be formatting before returning. |
5959
| $returnCellRef | boolean | false | False - Return a simple enumerated array of rows and columns (indexed by number counting from zero)<br />True - Return rows and columns indexed by their actual row and column IDs. |
60+
| $ignoreHidden | boolean | false | True - Ignore hidden rows and columns. |
61+
| $reduceArrays | boolean | false | True - If calculated value is an array, reduce it to top leftmost value. |
62+
| $lessFloatPrecision | boolean | false | True - PhpSpreadsheet 5.2+ - Floats, if formatted, will display as a more human-friendly but possibly less accurate value. |
6063

6164
### Dealing with empty rows
6265

src/PhpSpreadsheet/Shared/StringHelper.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -669,13 +669,16 @@ public static function strlenAllowNull(?string $string): int
669669
return strlen("$string");
670670
}
671671

672-
/** @param bool $convertBool If true, convert bool to locale-aware TRUE/FALSE rather than 1/null-string */
673-
public static function convertToString(mixed $value, bool $throw = true, string $default = '', bool $convertBool = false): string
672+
/**
673+
* @param bool $convertBool If true, convert bool to locale-aware TRUE/FALSE rather than 1/null-string
674+
* @param bool $lessFloatPrecision If true, floats will be converted to a more human-friendly but less computationally accurate value
675+
*/
676+
public static function convertToString(mixed $value, bool $throw = true, string $default = '', bool $convertBool = false, bool $lessFloatPrecision = false): string
674677
{
675678
if ($convertBool && is_bool($value)) {
676679
return $value ? Calculation::getTRUE() : Calculation::getFALSE();
677680
}
678-
if (is_float($value)) {
681+
if (is_float($value) && !$lessFloatPrecision) {
679682
$string = (string) $value;
680683
// look out for scientific notation
681684
if (!Preg::isMatch('/[^-+0-9.]/', $string)) {

src/PhpSpreadsheet/Style/NumberFormat.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -467,12 +467,13 @@ public function getHashCode(): string
467467
* @param string $format Format code: see = self::FORMAT_* for predefined values;
468468
* or can be any valid MS Excel custom format string
469469
* @param ?mixed[] $callBack Callback function for additional formatting of string
470+
* @param bool $lessFloatPrecision If true, unstyled floats will be converted to a more human-friendly but less computationally accurate value
470471
*
471472
* @return string Formatted string
472473
*/
473-
public static function toFormattedString(mixed $value, string $format, ?array $callBack = null): string
474+
public static function toFormattedString(mixed $value, string $format, ?array $callBack = null, bool $lessFloatPrecision = false): string
474475
{
475-
return NumberFormat\Formatter::toFormattedString($value, $format, $callBack);
476+
return NumberFormat\Formatter::toFormattedString($value, $format, $callBack, $lessFloatPrecision);
476477
}
477478

478479
/** @return mixed[] */

src/PhpSpreadsheet/Style/NumberFormat/Formatter.php

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,11 @@ private static function splitFormatForSectionSelection(array $sections, mixed $v
119119
* @param string $format Format code: see = self::FORMAT_* for predefined values;
120120
* or can be any valid MS Excel custom format string
121121
* @param null|array<mixed>|callable $callBack Callback function for additional formatting of string
122+
* @param bool $lessFloatPrecision If true, unstyled floats will be converted to a more human-friendly but less computationally accurate value
122123
*
123124
* @return string Formatted string
124125
*/
125-
public static function toFormattedString($value, string $format, null|array|callable $callBack = null): string
126+
public static function toFormattedString($value, string $format, null|array|callable $callBack = null, bool $lessFloatPrecision = false): string
126127
{
127128
while (is_array($value)) {
128129
$value = array_shift($value);
@@ -135,13 +136,13 @@ public static function toFormattedString($value, string $format, null|array|call
135136
$formatx = str_replace('\"', self::QUOTE_REPLACEMENT, $format);
136137
if (preg_match(self::SECTION_SPLIT, $format) === 0 && preg_match(self::SYMBOL_AT, $formatx) === 1) {
137138
if (!str_contains($format, '"')) {
138-
return str_replace('@', StringHelper::convertToString($value), $format);
139+
return str_replace('@', StringHelper::convertToString($value, lessFloatPrecision: $lessFloatPrecision), $format);
139140
}
140141
//escape any dollar signs on the string, so they are not replaced with an empty value
141142
$value = str_replace(
142143
['$', '"'],
143144
['\$', self::QUOTE_REPLACEMENT],
144-
StringHelper::convertToString($value)
145+
StringHelper::convertToString($value, lessFloatPrecision: $lessFloatPrecision)
145146
);
146147

147148
return str_replace(
@@ -153,14 +154,18 @@ public static function toFormattedString($value, string $format, null|array|call
153154

154155
// If we have a text value, return it "as is"
155156
if (!is_numeric($value)) {
156-
return StringHelper::convertToString($value);
157+
return StringHelper::convertToString($value, lessFloatPrecision: $lessFloatPrecision);
157158
}
158159

159160
// For 'General' format code, we just pass the value although this is not entirely the way Excel does it,
160161
// it seems to round numbers to a total of 10 digits.
161162
if (($format === NumberFormat::FORMAT_GENERAL) || ($format === NumberFormat::FORMAT_TEXT)) {
163+
if (is_float($value) && $lessFloatPrecision) {
164+
return self::adjustSeparators((string) $value);
165+
}
166+
162167
return self::adjustSeparators(
163-
StringHelper::convertToString($value)
168+
StringHelper::convertToString($value, lessFloatPrecision: $lessFloatPrecision)
164169
);
165170
}
166171

src/PhpSpreadsheet/Worksheet/Worksheet.php

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2938,12 +2938,15 @@ public function fromArray(array $source, mixed $nullValue = null, string $startC
29382938
}
29392939

29402940
/**
2941+
* @param bool $calculateFormulas Whether to calculate cell's value if it is a formula.
29412942
* @param null|bool|float|int|RichText|string $nullValue value to use when null
2943+
* @param bool $formatData Whether to format data according to cell's style.
2944+
* @param bool $lessFloatPrecision If true, formatting unstyled floats will convert them to a more human-friendly but less computationally accurate value
29422945
*
29432946
* @throws Exception
29442947
* @throws \PhpOffice\PhpSpreadsheet\Calculation\Exception
29452948
*/
2946-
protected function cellToArray(Cell $cell, bool $calculateFormulas, bool $formatData, mixed $nullValue): mixed
2949+
protected function cellToArray(Cell $cell, bool $calculateFormulas, bool $formatData, mixed $nullValue, bool $lessFloatPrecision = false): mixed
29472950
{
29482951
$returnValue = $nullValue;
29492952

@@ -2960,7 +2963,8 @@ protected function cellToArray(Cell $cell, bool $calculateFormulas, bool $format
29602963
$returnValuex = $returnValue;
29612964
$returnValue = NumberFormat::toFormattedString(
29622965
$returnValuex,
2963-
$style->getNumberFormat()->getFormatCode() ?? NumberFormat::FORMAT_GENERAL
2966+
$style->getNumberFormat()->getFormatCode() ?? NumberFormat::FORMAT_GENERAL,
2967+
lessFloatPrecision: $lessFloatPrecision
29642968
);
29652969
}
29662970
}
@@ -2978,6 +2982,8 @@ protected function cellToArray(Cell $cell, bool $calculateFormulas, bool $format
29782982
* True - Return rows and columns indexed by their actual row and column IDs
29792983
* @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden.
29802984
* True - Don't return values for rows/columns that are defined as hidden.
2985+
* @param bool $reduceArrays If true and result is a formula which evaluates to an array, reduce it to the top leftmost value.
2986+
* @param bool $lessFloatPrecision If true, formatting unstyled floats will convert them to a more human-friendly but less computationally accurate value
29812987
*
29822988
* @return mixed[][]
29832989
*/
@@ -2988,12 +2994,13 @@ public function rangeToArray(
29882994
bool $formatData = true,
29892995
bool $returnCellRef = false,
29902996
bool $ignoreHidden = false,
2991-
bool $reduceArrays = false
2997+
bool $reduceArrays = false,
2998+
bool $lessFloatPrecision = false
29922999
): array {
29933000
$returnValue = [];
29943001

29953002
// Loop through rows
2996-
foreach ($this->rangeToArrayYieldRows($range, $nullValue, $calculateFormulas, $formatData, $returnCellRef, $ignoreHidden, $reduceArrays) as $rowRef => $rowArray) {
3003+
foreach ($this->rangeToArrayYieldRows($range, $nullValue, $calculateFormulas, $formatData, $returnCellRef, $ignoreHidden, $reduceArrays, $lessFloatPrecision) as $rowRef => $rowArray) {
29973004
/** @var int $rowRef */
29983005
$returnValue[$rowRef] = $rowArray;
29993006
}
@@ -3012,6 +3019,8 @@ public function rangeToArray(
30123019
* True - Return rows and columns indexed by their actual row and column IDs
30133020
* @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden.
30143021
* True - Don't return values for rows/columns that are defined as hidden.
3022+
* @param bool $reduceArrays If true and result is a formula which evaluates to an array, reduce it to the top leftmost value.
3023+
* @param bool $lessFloatPrecision If true, formatting unstyled floats will convert them to a more human-friendly but less computationally accurate value
30153024
*
30163025
* @return mixed[][]
30173026
*/
@@ -3022,14 +3031,15 @@ public function rangesToArray(
30223031
bool $formatData = true,
30233032
bool $returnCellRef = false,
30243033
bool $ignoreHidden = false,
3025-
bool $reduceArrays = false
3034+
bool $reduceArrays = false,
3035+
bool $lessFloatPrecision = false,
30263036
): array {
30273037
$returnValue = [];
30283038

30293039
$parts = explode(',', $ranges);
30303040
foreach ($parts as $part) {
30313041
// Loop through rows
3032-
foreach ($this->rangeToArrayYieldRows($part, $nullValue, $calculateFormulas, $formatData, $returnCellRef, $ignoreHidden, $reduceArrays) as $rowRef => $rowArray) {
3042+
foreach ($this->rangeToArrayYieldRows($part, $nullValue, $calculateFormulas, $formatData, $returnCellRef, $ignoreHidden, $reduceArrays, $lessFloatPrecision) as $rowRef => $rowArray) {
30333043
/** @var int $rowRef */
30343044
$returnValue[$rowRef] = $rowArray;
30353045
}
@@ -3049,6 +3059,8 @@ public function rangesToArray(
30493059
* True - Return rows and columns indexed by their actual row and column IDs
30503060
* @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden.
30513061
* True - Don't return values for rows/columns that are defined as hidden.
3062+
* @param bool $reduceArrays If true and result is a formula which evaluates to an array, reduce it to the top leftmost value.
3063+
* @param bool $lessFloatPrecision If true, formatting unstyled floats will convert them to a more human-friendly but less computationally accurate value
30523064
*
30533065
* @return Generator<array<mixed>>
30543066
*/
@@ -3059,7 +3071,8 @@ public function rangeToArrayYieldRows(
30593071
bool $formatData = true,
30603072
bool $returnCellRef = false,
30613073
bool $ignoreHidden = false,
3062-
bool $reduceArrays = false
3074+
bool $reduceArrays = false,
3075+
bool $lessFloatPrecision = false
30633076
) {
30643077
$range = Validations::validateCellOrCellRange($range);
30653078

@@ -3125,7 +3138,7 @@ public function rangeToArrayYieldRows(
31253138
$columnRef = $returnCellRef ? $col : ($thisCol - $minColInt);
31263139
$cell = $this->cellCollection->get("{$col}{$thisRow}");
31273140
if ($cell !== null) {
3128-
$value = $this->cellToArray($cell, $calculateFormulas, $formatData, $nullValue);
3141+
$value = $this->cellToArray($cell, $calculateFormulas, $formatData, $nullValue, lessFloatPrecision: $lessFloatPrecision);
31293142
if ($reduceArrays) {
31303143
while (is_array($value)) {
31313144
$value = array_shift($value);
@@ -3226,6 +3239,8 @@ private function validateNamedRange(string $definedName, bool $returnNullIfInval
32263239
* True - Return rows and columns indexed by their actual row and column IDs
32273240
* @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden.
32283241
* True - Don't return values for rows/columns that are defined as hidden.
3242+
* @param bool $reduceArrays If true and result is a formula which evaluates to an array, reduce it to the top leftmost value.
3243+
* @param bool $lessFloatPrecision If true, formatting unstyled floats will convert them to a more human-friendly but less computationally accurate value
32293244
*
32303245
* @return mixed[][]
32313246
*/
@@ -3236,7 +3251,8 @@ public function namedRangeToArray(
32363251
bool $formatData = true,
32373252
bool $returnCellRef = false,
32383253
bool $ignoreHidden = false,
3239-
bool $reduceArrays = false
3254+
bool $reduceArrays = false,
3255+
bool $lessFloatPrecision = false
32403256
): array {
32413257
$retVal = [];
32423258
$namedRange = $this->validateNamedRange($definedName);
@@ -3245,7 +3261,7 @@ public function namedRangeToArray(
32453261
$cellRange = str_replace('$', '', $cellRange);
32463262
$workSheet = $namedRange->getWorksheet();
32473263
if ($workSheet !== null) {
3248-
$retVal = $workSheet->rangeToArray($cellRange, $nullValue, $calculateFormulas, $formatData, $returnCellRef, $ignoreHidden, $reduceArrays);
3264+
$retVal = $workSheet->rangeToArray($cellRange, $nullValue, $calculateFormulas, $formatData, $returnCellRef, $ignoreHidden, $reduceArrays, $lessFloatPrecision);
32493265
}
32503266
}
32513267

@@ -3262,6 +3278,8 @@ public function namedRangeToArray(
32623278
* True - Return rows and columns indexed by their actual row and column IDs
32633279
* @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden.
32643280
* True - Don't return values for rows/columns that are defined as hidden.
3281+
* @param bool $reduceArrays If true and result is a formula which evaluates to an array, reduce it to the top leftmost value.
3282+
* @param bool $lessFloatPrecision If true, formatting unstyled floats will convert them to a more human-friendly but less computationally accurate value
32653283
*
32663284
* @return mixed[][]
32673285
*/
@@ -3271,7 +3289,8 @@ public function toArray(
32713289
bool $formatData = true,
32723290
bool $returnCellRef = false,
32733291
bool $ignoreHidden = false,
3274-
bool $reduceArrays = false
3292+
bool $reduceArrays = false,
3293+
bool $lessFloatPrecision = false
32753294
): array {
32763295
// Garbage collect...
32773296
$this->garbageCollect();
@@ -3282,7 +3301,7 @@ public function toArray(
32823301
$maxRow = $this->getHighestRow();
32833302

32843303
// Return
3285-
return $this->rangeToArray("A1:{$maxCol}{$maxRow}", $nullValue, $calculateFormulas, $formatData, $returnCellRef, $ignoreHidden, $reduceArrays);
3304+
return $this->rangeToArray("A1:{$maxCol}{$maxRow}", $nullValue, $calculateFormulas, $formatData, $returnCellRef, $ignoreHidden, $reduceArrays, $lessFloatPrecision);
32863305
}
32873306

32883307
/**

tests/PhpSpreadsheetTests/Shared/Issue1324Test.php

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010

1111
class Issue1324Test extends AbstractFunctional
1212
{
13-
protected static int $version = 80400;
14-
1513
public function testPrecision(): void
1614
{
1715
$string1 = '753.149999999999';
@@ -45,4 +43,55 @@ public function testCsv(): void
4543
self::assertStringContainsString($string1, $output);
4644
$spreadsheet->disconnectWorksheets();
4745
}
46+
47+
public static function testLessPrecision(): void
48+
{
49+
$spreadsheet = new Spreadsheet();
50+
$sheet = $spreadsheet->getActiveSheet();
51+
$inArray = [
52+
[123.4, 753.149999999999, 12.04, 224.245],
53+
[12345.6789, 44125.62188657407387],
54+
[true, 16, 'hello'],
55+
[753.149999999999, 44125.62188657407387],
56+
];
57+
$sheet->fromArray($inArray, null, 'A1', true);
58+
$sheet->getStyle('A4:B4')->getNumberFormat()
59+
->setFormatCode('#.##');
60+
$precise = $sheet->toArray();
61+
$expectedPrecise = [
62+
['123.40000000000001', '753.14999999999895', '12.039999999999999', '224.245'],
63+
['12345.67890000000079', '44125.62188657407387', null, null],
64+
['TRUE', '16', 'hello', null],
65+
['753.15', '44125.62', null, null],
66+
];
67+
self::assertSame($expectedPrecise, $precise);
68+
$lessPrecise = $sheet->toArray(lessFloatPrecision: true);
69+
$expectedLessPrecise = [
70+
['123.4', '753.15', '12.04', '224.245'],
71+
['12345.6789', '44125.621886574', null, null],
72+
['TRUE', '16', 'hello', null],
73+
['753.15', '44125.62', null, null],
74+
];
75+
self::assertSame($expectedLessPrecise, $lessPrecise);
76+
$spreadsheet->disconnectWorksheets();
77+
$preg = '/^[-+]?\d+[.]\d+$/';
78+
$diffs = [];
79+
for ($i = 0; $i < 2; ++$i) {
80+
$preciseRow = $precise[$i];
81+
$lessPreciseRow = $lessPrecise[$i];
82+
$entries = count($preciseRow);
83+
for ($j = 0; $j < $entries; ++$j) {
84+
$comparand1 = $preciseRow[$j];
85+
$comparand2 = $lessPreciseRow[$j];
86+
if (preg_match($preg, $comparand1 ?? '') && preg_match($preg, $comparand2 ?? '')) {
87+
$comparand1 = (float) $comparand1;
88+
$comparand2 = (float) $comparand2;
89+
}
90+
if ($comparand1 !== $comparand2) {
91+
$diffs[] = [$i, $j];
92+
}
93+
}
94+
}
95+
self::assertSame([[0, 1], [1, 1]], $diffs, 'cells which do not evaluate to same float value');
96+
}
4897
}

0 commit comments

Comments
 (0)