This is an old revision of the document!
PHP RFC: More precise float value handling
- Version: 1.0
- Date: 2015-07-30
- Author: Yasuo Ohgaki yohgaki@ohgaki.net, Jakub Zelenka bukka@php.net
- Status: Under Discussion
- First Published at: http://wiki.php.net/rfc/precise_float_value
Introduction
This RFC is based on the discussion about displaying float values in json_encode and proposes more precise float value handling overall.
JSON is used to exchange data between systems. Although JSON RFC "6 Numbers" does not require specific implementation for float/int type, float value should be handled as precise as possible by default.
Currently, json_encode() uses EG(precision) which is set to 14. That means that 14 digits max are used for displaying (printing) the number. IEEE 754 double supports higher precision and serialize()/var_export() uses PG(serialize_precision)=17 to be more precise. Since json_encode() uses EG(precision), json_encode() removes lower digits of fraction parts and destroys original value even if PHP's float could hold more precise float value.
<?php $j = '{ "v": 0.1234567890123456789 }'; var_dump(json_decode($j)); var_dump(json_encode(json_decode($j))); ini_set('precision', 20); var_dump(json_decode($j)); var_dump(json_encode(json_decode($j))); var_dump(0.1234567890123456789); ?>
object(stdClass)#1 (1) { ["v"]=> float(0.12345678901235) } string(22) "{"v":0.12345678901235}" object(stdClass)#1 (1) { ["v"]=> float(0.12345678901234567737) } string(28) "{"v":0.12345678901234567737}" float(0.12345678901234567737)
PHP's float type stores “raw” IEEE 754 double and could display accurate fraction value up to 17 digits.
Current PHP outputs meaningless values for oversized EG(precision)/PG(serialize_precision).
<?php $v = 0.12345678901234567890; var_dump($v); ini_set('precision', 100); var_dump($v); ?>
float(0.12345678901235) float(0.12345678901234567736988623209981597028672695159912109375)
That is caused by used mode for double to string conversion.
Proposal
This RFC proposes to introduce EG(precision)=-1 and PG(serialize_precision)=-1 that uses zend_dtoa()'s mode 0 which uses better algorigthm for rounding float numbers (-1 is used to indicate 0 mode).
Since JSON is used extensively for web apps, we may have JSON specific INI setting for better compatibility and ease of use. It means to add JSON_G(json.precision) which will be used for JSON module specific precision INI setting.
Followings are sample codes and outputs of the proposed patch.
NEW behavior
<?php $v = 10.0000000000001; ini_set('precision', -1); ini_set('serialize_precision', -1); ini_set('json.precision', -1); var_dump($v); echo var_export($v, true), PHP_EOL; echo json_encode($v), PHP_EOL; echo $v, PHP_EOL; ?>
float(10.0000000000001) 10.0000000000001 10.0000000000001 10.0000000000001
OLD behavior
<?php $v = 10.00000000000001; ini_set('precision', 14); ini_set('serialize_precision', 17); ini_set('json.precision', 14); var_dump($v); echo var_export($v, true), PHP_EOL; echo json_encode($v), PHP_EOL; echo $v, PHP_EOL; ?>
float(10) 10.000000000000011 10 10
Please note that IEEE float cannot store exactly precise values. e.g. Result of “10/3” - see phpt of the patch. Even with this proposal, there will be rounding errors, but the behavior becomes similar to other languages and values are more precise in many cases.
Backward Incompatible Changes
Setting mode 0 as default can mean that the rounding will be more precise which also means that the rounding might be different in var_export()/serialize().
The BC break could happen only if someone would rely on exact output but that shouldn't be the case. All our existing tests passes even when 0 mode is used.
None when old INI value is used.
Proposed PHP Version(s)
- PHP 7.1
RFC Impact
To SAPIs
None.
To Existing Extensions
PHP overall
- 0 mode (EG(precision)= -1) float outputs values rounded to nearest.
Standard module
- serialize(), var_export() - Use PG(serialize_precision) and 0 mode by default.
JSON
- json_encode() - Use ini selected from the vote result and 0 mode by default.
To Opcache
Not affected.
New Constants
None.
php.ini Defaults
precision
- hardcoded default values : 14 Unmodified
- php.ini-development values : 14 Unmodified
- php.ini-production values : 14 Unmodified
serialize_precision
- hardcoded default values : -1
- php.ini-development values : -1
- php.ini-production values : -1
json.precision (if accepted)
- hardcoded default values : -1
- php.ini-development values : -1
- php.ini-production values : -1
Open Issues
None.
Unaffected PHP Functionality
PHP uses “raw” IEEE 754 value internally regardless of precision settings. Therefore, this RFC does not affect internal computation.
Future Scope
WDDX
- wddx_serialize_vars/value() - Use PG(serialize_precision) and 0 mode. It uses EG(precision) currently.
XML_RPC
- xmlrpc_encode() - Use PG(serialize_precision) and 0 mode. It uses EG(precision) currently.
Proposed Voting Choices
Requires a 50%+1 majority
There will be two votings
- whether to introduce mode 0 as default
- what precision ini should be used for json (precision, serialize_precision, json.precision).
Patches and Tests
Implementation
After the project is implemented, this section should contain
- the version(s) it was merged to
- a link to the git commit(s)
- a link to the PHP manual entry for the feature
References
Rejected Features
Keep this updated with features that were discussed on the mail lists.