diff --git a/src/PhpSpreadsheet/Cell/DefaultValueBinder.php b/src/PhpSpreadsheet/Cell/DefaultValueBinder.php index 10c5c93c59..f636befcfb 100644 --- a/src/PhpSpreadsheet/Cell/DefaultValueBinder.php +++ b/src/PhpSpreadsheet/Cell/DefaultValueBinder.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheet\Cell; +use Composer\Pcre\Preg; use DateTimeInterface; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalculationException; @@ -12,6 +13,9 @@ class DefaultValueBinder implements IValueBinder { + // 123 456 789 012 345 + private const FIFTEEN_NINES = 999_999_999_999_999; + /** * Bind value to a cell. * @@ -49,6 +53,9 @@ public static function dataTypeForValue(mixed $value): string if ($value === null) { return DataType::TYPE_NULL; } + if (is_int($value) && abs($value) > self::FIFTEEN_NINES) { + return DataType::TYPE_STRING; + } if (is_float($value) || is_int($value)) { return DataType::TYPE_NUMERIC; } @@ -89,13 +96,18 @@ public static function dataTypeForValue(mixed $value): string return DataType::TYPE_FORMULA; } - if (preg_match('/^[\+\-]?(\d+\.?\d*|\d*\.?\d+)([Ee][\-\+]?[0-2]?\d{1,3})?$/', $value)) { + if (Preg::isMatch('/^[\+\-]?(\d+\.?\d*|\d*\.?\d+)([Ee][\-\+]?[0-2]?\d{1,3})?$/', $value)) { $tValue = ltrim($value, '+-'); if (strlen($tValue) > 1 && $tValue[0] === '0' && $tValue[1] !== '.') { return DataType::TYPE_STRING; - } elseif ((!str_contains($value, '.')) && ($value > PHP_INT_MAX)) { - return DataType::TYPE_STRING; - } elseif (!is_numeric($value)) { + } + if (!Preg::isMatch('/[eE.]/', $value)) { + $aValue = abs((float) $value); + if ($aValue > self::FIFTEEN_NINES) { + return DataType::TYPE_STRING; + } + } + if (!is_numeric($value)) { return DataType::TYPE_STRING; } diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/FloatsRetainedTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/FloatsRetainedTest.php index c10a1b9b47..fa9205154c 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/FloatsRetainedTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/FloatsRetainedTest.php @@ -8,16 +8,21 @@ use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Writer\Xlsx as Writer; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class FloatsRetainedTest extends TestCase { - #[\PHPUnit\Framework\Attributes\DataProvider('providerIntyFloatsRetainedByWriter')] - public function testIntyFloatsRetainedByWriter(float|int $value): void + #[DataProvider('providerIntyFloatsRetainedByWriter')] + public function testIntyFloatsRetainedByWriter(float|int $value, mixed $expected = null): void { + if ($expected === null) { + $expected = $value; + } $outputFilename = File::temporaryFilename(); $spreadsheet = new Spreadsheet(); - $spreadsheet->getActiveSheet()->getCell('A1')->setValue($value); + $spreadsheet->getActiveSheet() + ->getCell('A1')->setValue($value); $writer = new Writer($spreadsheet); $writer->save($outputFilename); @@ -27,7 +32,11 @@ public function testIntyFloatsRetainedByWriter(float|int $value): void $spreadsheet2 = $reader->load($outputFilename); unlink($outputFilename); - self::assertSame($value, $spreadsheet2->getActiveSheet()->getCell('A1')->getValue()); + self::assertSame( + $expected, + $spreadsheet2->getActiveSheet() + ->getCell('A1')->getValue() + ); $spreadsheet2->disconnectWorksheets(); } @@ -44,10 +53,10 @@ public static function providerIntyFloatsRetainedByWriter(): array [1.3e-10], [1e10], [3.00000000000000000001], - [99999999999999999], - [99999999999999999.0], - [999999999999999999999999999999999999999999], - [999999999999999999999999999999999999999999.0], + 'int but too much precision for Excel' => [99_999_999_999_999_999, '99999999999999999'], + [99_999_999_999_999_999.0], + 'int > PHP_INT_MAX so stored as float' => [999_999_999_999_999_999_999_999_999_999_999_999_999_999], + [999_999_999_999_999_999_999_999_999_999_999_999_999_999.0], ]; } }