Hello internals,
This is the second round of the discussion regarding arbitrary precision
scalar type integration into PHP. The previous part:
https://marc.info/?l=php-internals&m=168250492216838&w=2 was initiated by
me before deep diving into the work with decimals in PHP. After 6 months of
working, I would like to update my proposal taking into account my
experience and the previous discussion.
Today's alternatives and their problems are the following.
bcmath:
- Workaround: using string type.
- Unintuitive function calls instead of regular math operations.
- Unintuitive strings instead of numbers. People want to work with numbers.
- Can not use proper type-hinting.
- Can use PHP's basic type coercions.
Ext-decimal:
- Third-party extension.
- Workaround: implements the Decimal class that allows basic regular math
operations.
- Requires using class methods for the rest of math operations.
- The latest release was in 2019 and there's a danger that it will be
unmaintained and not compatible with the future PHP releases.
- The php-decimal documentation website is currently down.
- Since objects are always casted to true when not null, "(bool)
Decimal(0)" will equal to true which is not intuitive.
- IDEs are often confused when you use math operations on objects while the
code works fine.
GMP:
- Workaround: implements the GMP class that allows basic math operations.
- Requires using separate functions for the rest of operations.
- Objects are always casted to true, GMP(0) will equal to true.
Accounting for all of the above, I suggest adding a native numeric scalar
arbitrary precision type called "decimal". Below are the preliminary
requirements for implementation.
Decimal values can be created from literals by specifying a modifier or
using the (decimal) typecast:
$v = 0.2d;
$v = (decimal) 0.2; // Creates a decimal value without intermediary float
It uses the precision and scale defined in php.ini.
The "decimal" typehint allows to define custom precision and scale:
decimal(20,5). It accepts regular expressions returning ints in the
execution context. It accepts int constants and literals in class field and
function argument definitions.
New functions added: get_scale and get_precision to return corresponding
values about a decimal value.
If decimal value with different scale and precision is going to be assigned
to a variable or parameter with smaller scale or precision, it first tries
to convert the value. If it's not possible, then an exception is thrown
like "Can not convert decimal (a, b) xxxxx.yyyy to decimal(c, d)". If
possible, it performs the conversion and generates a warning like
"Assigning decimal(a, b) to decimal(c, d) may be not possible with some
values".
It works the same as "float" in terms of its usage and type casting except
for one thing. Float value can be passed to a decimal argument or
typecasted with a warning like "Float to decimal conversion may incur
unexpected results".
Decimal to float conversion is allowed and smooth:
function f (float $value) {}
f(0.2);
f(0.2d); // allowed with no warnings
Function "str_to_decimal" added to convert string representation of numbers
to decimals.
Typecast from string to decimal works the same as the "str_to_decimal"
function.
Function "float_to_decimal" added to explicitly convert floats to decimals.
It performs float to string conversions using php.ini settings as defaults
but also accepts parameters to configure the conversion. Then, it converts
string to decimal. Since the main problem of float to decimal conversion is
that we don't know the exact result until we use some rounding when
transforming it to a human-readable format, it looks like the step of the
conversion to a string is inevitable. Any more optimized algorithms are
welcome.
Explicit typecast from float to decimal works the same as
"float_to_decimal" function with all default values but also throws a
warning. This is to encourage users to use explicit conversion with the
"float_to_decimal" function and control the results.
Literal numbers in the code are converted to floats by default. If
prepended by the "(decimal)" typecast, the decimal result is produced
without an intermediary float.
New declare directive "default_decimal" is added. When used, literals and
math operations return decimal by default instead of float. This is to
simplify creating source files working with decimals only.
New language construct "as_decimal()" is added to produce decimal math
results for literals and math operations instead of float without
intermediary float:
$var = 5 / 2; // returns float 2.5
$var = as_decimal(5 / 2); // returns decimal 2.5
This is a kind of "default_decimal" for a specific operation.
If mixed float and decimal operands are used in a math operation, decimal
is converted to float by default. If "default_decimal" directive or
"as_decimal()" construct is used, float is converted to decimal (with a
warning):
$f = (float) 0.2;
$d = (decimal) 0.2;
$r = $f + $d; // returns float result by default
$r = as_decimal($f + $d); // returns decimal result with a warning about
implicit float to decimal conversion
All builtin functions that currently accept float also accept decimal. So
users don't need to care about separate function sets, and PHP developers
don't need to maintain separate sets of functions. If such functions get
the decimal parameter, they return decimal. If they have more than one
float parameter and mixed float and decimal passed, decimals converted to
float by default. If "default_decimal" or "as_decimal" used, float is
converted to decimal with the warning.
The new type uses libmpdec internally to perform decimal calculations (same
as Python).
All of the points above are subject to discussions, it is not an RFC
candidate right now. So please share your opinions.
I know that the implementation of this will require a lot of work. But I
don't think this is a stopper from formulating the requirements. Sometimes,
any project requires big changes to move forward. I'm pretty sure this
functionality will move PHP to the next level and expand its area of
applications. My thoughts here are mostly from the user's perspective, I'm
not so familiar with PHP internal implementation. But I think this feature
can be a good goal for PHP 9.
--
Best regards,
Alex Pravdin