From f3ca367efa5a44161ca6fa3a2a9fa3b9ed24cbb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Tue, 30 Aug 2016 15:45:53 +0200 Subject: [PATCH] Fix number formatting with different settings of precision in PHP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fixed Util::formatNumber to avoid rounding issues to mess up display - force precision = 14 to ensure we get sane behavior With precision set to higher value many things do not behave as expected producing strange numbers in various places. For example with precision=19 you get: php > echo round(1.2, 2); 1.199999999999999956 Fixes #12303 Signed-off-by: Michal Čihař --- ChangeLog | 1 + libraries/Util.php | 16 +++---- libraries/common.inc.php | 7 +++ test/bootstrap-dist.php | 7 +++ .../common/PMA_formatNumberByteDown_test.php | 45 ++++++++++++++++--- 5 files changed, 60 insertions(+), 16 deletions(-) diff --git a/ChangeLog b/ChangeLog index 79fa66e378..5e2f3532e3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -19,6 +19,7 @@ phpMyAdmin - ChangeLog - issue #12251 Automatically save SQL query in browser local storage rather than in cookie - issue #12292 Unable to edit transformations - issue #12502 Remove unused paramenter when connecting to MySQLi +- issue #12303 Fix number formatting with different settings of precision in PHP 4.6.4 (2016-08-16) - issue [security] Weaknesses with cookie encryption, see PMASA-2016-29 diff --git a/libraries/Util.php b/libraries/Util.php index 538fe808cf..5e2a4a3c5b 100644 --- a/libraries/Util.php +++ b/libraries/Util.php @@ -1604,17 +1604,13 @@ class Util $value = round($value / (self::pow(1000, $d, 'pow') / $dh)) /$dh; $unit = $units[$d]; - // If we don't want any zeros after the comma just add the thousand separator - if ($noTrailingZero) { - $localizedValue = self::localizeNumber( - preg_replace('/(?<=\d)(?=(\d{3})+(?!\d))/', ',', $value) - ); - } else { - //number_format is not multibyte safe, str_replace is safe - $localizedValue = self::localizeNumber( - number_format($value, $digits_right) - ); + // number_format is not multibyte safe, str_replace is safe + $formattedValue = number_format($value, $digits_right); + // If we don't want any zeros, remove them now + if ($noTrailingZero && strpos($formattedValue, '.') !== false) { + $formattedValue = preg_replace('/\.?0+$/', '', $formattedValue); } + $localizedValue = self::localizeNumber($formattedValue); if ($originalValue != 0 && floatval($value) == 0) { return ' <' . self::localizeNumber((1 / self::pow(10, $digits_right))) diff --git a/libraries/common.inc.php b/libraries/common.inc.php index 3c6134afcf..0a04b5e618 100644 --- a/libraries/common.inc.php +++ b/libraries/common.inc.php @@ -107,6 +107,13 @@ PMA_checkExtensions(); ini_set('default_charset', 'utf-8'); mb_internal_encoding('utf-8'); +/** + * Set precision to sane value, with higher values + * things behave slightly unexpectedly, for example + * round(1.2, 2) returns 1.199999999999999956. + */ +ini_set('precision', 14); + /** * the relation lib, tracker needs it */ diff --git a/test/bootstrap-dist.php b/test/bootstrap-dist.php index 1b2e45059f..0188e49b05 100644 --- a/test/bootstrap-dist.php +++ b/test/bootstrap-dist.php @@ -6,6 +6,13 @@ * @package PhpMyAdmin-test */ +/** + * Set precision to sane value, with higher values + * things behave slightly unexpectedly, for example + * round(1.2, 2) returns 1.199999999999999956. + */ +ini_set('precision', 14); + // Let PHP complain about all errors error_reporting(E_ALL); diff --git a/test/libraries/common/PMA_formatNumberByteDown_test.php b/test/libraries/common/PMA_formatNumberByteDown_test.php index 138db8c49d..604e59b3e3 100644 --- a/test/libraries/common/PMA_formatNumberByteDown_test.php +++ b/test/libraries/common/PMA_formatNumberByteDown_test.php @@ -70,6 +70,30 @@ class PMA_FormatNumberByteDown_Test extends PHPUnit_Framework_TestCase array(0.003, 3, 3, '3 m'), array(-0.003, 6, 0, '-3,000 µ'), array(100.98, 0, 2, '100.98'), + array(21010101, 0, 2, '21,010,101.00'), + array(20000, 2, 2, '20 k'), + array(20011, 2, 2, '20.01 k'), + ); + } + + /** + * Core test for formatNumber + * + * + * @param float $a Value to format + * @param int $b Sensitiveness + * @param int $c Number of decimals to retain + * @param array $d Expected value + * + * @return void + */ + private function assertFormatNumber($a, $b, $c, $d) + { + $this->assertEquals( + $d, + (string) PMA\libraries\Util::formatNumber( + $a, $b, $c, false + ) ); } @@ -87,12 +111,21 @@ class PMA_FormatNumberByteDown_Test extends PHPUnit_Framework_TestCase */ public function testFormatNumber($a, $b, $c, $d) { - $this->assertEquals( - $d, - (string) PMA\libraries\Util::formatNumber( - $a, $b, $c, false - ) - ); + $this->assertFormatNumber($a, $b, $c, $d); + + // Test with various precisions + $old_precision = ini_get('precision'); + ini_set('precision', 20); + $this->assertFormatNumber($a, $b, $c, $d); + ini_set('precision', 14); + $this->assertFormatNumber($a, $b, $c, $d); + ini_set('precision', 10); + $this->assertFormatNumber($a, $b, $c, $d); + ini_set('precision', 5); + $this->assertFormatNumber($a, $b, $c, $d); + ini_set('precision', -1); + $this->assertFormatNumber($a, $b, $c, $d); + ini_set('precision', $old_precision); } /**