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
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 floatIt 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.5This 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 conversionAll 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
Someone tangential to your proposal, have you given any thought to implementing this similar to how Python treats numbers?
In Python, integers have unlimited precision. I’m not sure how they store the values internally, but to users of Python, any sequence of numbers is an int
, no matter how long it is.
For example, this multiplies the max 64-bit, signed integer by 2, and the result is still an int
:
>>> i = 9223372036854775807 * 2
>>> i
18446744073709551614
>>> type(i)
<class 'int’>
And this is a really, really, really long number that’s still an int
:
>>> x = 9223372036854775807922337203685477580792233720368547758079223372036854775807
>>> x
9223372036854775807922337203685477580792233720368547758079223372036854775807
>>> type(x)
<class 'int’>
Python has floating point numbers, but these use the double type in C, so they’re not arbitrary in length, like its integers. (Python uses libmpdec, as you’ve mentioned, with decimal.Decimal
.)
However, if we can achieve arbitrary-length integers, is there a reason we couldn’t also have arbitrary length floating point numbers?
Below a certain threshold, we could use the system’s native int and double for storage and math. Above a certain threshold, we could internally switch to a bundled library for handling arbitrary-precision integers and floats, and it would be seamless to the user, who would see only int
and float
types in PHP.
Anyway, this is something I’ve been thinking about for some time, but I haven’t taken the time to look into what it might take to implement it. It sounds like you have looked into it, so I’m interested to know whether you think what I’ve described here is feasible.
Cheers,
Ben
Cheers,
Ben
Someone tangential to your proposal, have you given any thought to
implementing this similar to how Python treats numbers?
Unfortunately, I don't know many details of numbers treatment in Python...
In Python, integers have unlimited precision. I’m not sure how they store
the values internally, but to users of Python, any sequence of numbers is
anint
, no matter how long it is.For example, this multiplies the max 64-bit, signed integer by 2, and the
result is still anint
:>>> i = 9223372036854775807 * 2 >>> i 18446744073709551614 >>> type(i) <class 'int’>
And this is a really, really, really long number that’s still an
int
:>>> x =
9223372036854775807922337203685477580792233720368547758079223372036854775807
>>> x9223372036854775807922337203685477580792233720368547758079223372036854775807
>>> type(x)
<class 'int’>
I'm not sure about integers, I mostly care about decimals :) Decimals can
hold big rounded ints as well. The limit on the maximum number here will be
the underlying library's limit.
However, if we can achieve arbitrary-length integers, is there a reason we
couldn’t also have arbitrary length floating point numbers?
I'm not familiar with arbitrary-length integers so I don't know what to say
here...
Below a certain threshold, we could use the system’s native int and double
for storage and math. Above a certain threshold, we could internally switch
to a bundled library for handling arbitrary-precision integers and floats,
and it would be seamless to the user, who would see onlyint
andfloat
types in PHP.
I guess this could be a topic of the next step discussion after "decimal"
introduction.
Anyway, this is something I’ve been thinking about for some time, but I
haven’t taken the time to look into what it might take to implement it. It
sounds like you have looked into it, so I’m interested to know whether you
think what I’ve described here is feasible.
I was only thinking about decimals and I'm not an expert in low-level
manipulation of numbers.
Best regards,
Alex.
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.
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.
You are going to run into some very difficult corners on this one. For the
last... 8 years i guess? I have been working on an arbitrary precision
library for PHP in userland. It utilizes BCMath, ext-decimal, and GMP,
depending on what is installed and what kind of operation you are
performing.
So, the functions that currently accept floats include functions such as
sin()
or atan()
. These functions are not at all trivial to do to
arbitrary precision. In fact, the VAST majority of the work I have done on
my library has been to handle cases like "What do I do if the user wants
2.921 ** 4.293810472934854?" or "How will I guarantee the precision that
the user requested for tan()
in an efficient way?"
Now, libmpdec is going to actually have implementations for most of this
stuff that you can call directly, but if the PHP function sin()
isn't
capable of outputting a decimal
, then this isn't really that much of an
improvement over the current situation. I mean, it's still something I
would support, but that's the area where some help from the engine goes a
VERY long way.
You're probably also going to need a way for a user to grab certain
constants to a given precision. I would think e and pi at the least.
I have a lot of experience working on this problem space in PHP, and am
definitely willing to help. I looked into doing a libmpdec PHP integration
several years ago, but decided that voters were unlikely to give it a fair
shot, and so decided against it. If you're willing to try and roll the ball
up the hill though, I'll definitely help you.
Jordan
On Thu, Dec 7, 2023 at 11:27 PM Jordan LeDoux jordan.ledoux@gmail.com
wrote:
You are going to run into some very difficult corners on this one. For the
last... 8 years i guess? I have been working on an arbitrary precision
library for PHP in userland. It utilizes BCMath, ext-decimal, and GMP,
depending on what is installed and what kind of operation you are
performing.So, the functions that currently accept floats include functions such as
sin()
oratan()
. These functions are not at all trivial to do to
arbitrary precision. In fact, the VAST majority of the work I have done on
my library has been to handle cases like "What do I do if the user wants
2.921 ** 4.293810472934854?" or "How will I guarantee the precision that
the user requested fortan()
in an efficient way?"Now, libmpdec is going to actually have implementations for most of this
stuff that you can call directly, but if the PHP functionsin()
isn't
capable of outputting adecimal
, then this isn't really that much of an
improvement over the current situation. I mean, it's still something I
would support, but that's the area where some help from the engine goes a
VERY long way.
In my proposal, builtin functions are capable of returning decimals if
they're fed with decimals.
You're probably also going to need a way for a user to grab certain
constants to a given precision. I would think e and pi at the least.
I have a lot of experience working on this problem space in PHP, and am
definitely willing to help. I looked into doing a libmpdec PHP integration
several years ago, but decided that voters were unlikely to give it a fair
shot, and so decided against it. If you're willing to try and roll the ball
up the hill though, I'll definitely help you.
Unfortunately, I'm not an expert in precise math. I definitely need help
from experts like you. I appreciate your readiness.
I understand that there are a lot of edge cases. I would propose to
implement basics at first and then decide what to do with edge cases.
Hello internals,
[...]
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.
This is incorrect, GMP object do not support casts to bool
See https://3v4l.org/LHpD1
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
I disagree with this part, floats can not represent decimal values properly
e.g.
$f1 = 0.1;
$f2 = 0.2;
$f3 = 0.3;
var_dump($f1 + $f2 === $f3);
will return false.
As such, floats are not at all compatible with decimals.
Moreover, the whole point of adding a warning when implicit conversions
from int to float happen was to be able to warn before elevating the
warning to a TypeError.
Therefore, introducing behaviour that warns instead of throwing a TypeError
is already a no-go from my PoV.
[...]
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.
This behaviour is rather suboptimal, I'd rather have literals be decimals,
as decimals to floats should always be a reasonable implicit coercion
considering we already do int to 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.
Please no, no new declare directives that affect engine behaviour.
Strict types was already a mistake because of this IMHO.
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.5This is a kind of "default_decimal" for a specific operation.
Again, this should return a decimal instead IMHO.
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 conversionAll 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.
Messing with the return value type has already proved controversial.
And as I said previously, I am dead against having implicit float to
decimal conversions.
Making the functions type generic is something that I am fine, however.
The new type uses libmpdec internally to perform decimal calculations
(same
as Python).
I really don't think we need arbitrary precision decimals.
I'm also not convinced using a floating point spec is the most sensible,
due to the rounding errors this is going to introduce.
The non-arbitrary IEEE 754-2008 specification cannot describe the decimal
123457.1467 exactly, which is frankly pointless.
Decimals, or more broadly rational numbers that, outside numerical
computations, that are going to be used are not going to need huge
denominators.
I've been thinking about this for a while, and I personally think that
rational numbers are just better than trying to support only decimals.
And considering we have 64 bits to play with for a new zval type splitting
the bits into a 32-bit unsigned integer for the numerator and an 32-bit
signed integer for the denominator should provide us with enough reasonable
rational numbers.
As any number that do not fit in this structure seems to be something
relegated to the world of numerical computation where floats are what are
mostly used anyway.
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.
Yes, this is a major project, and as said above I have also thought about
adding a rational number type.
But the main hurdle for this is changing the zval structure and applying
the change everywhere to the engine and extensions.
Best regards,
Gina P. Banyard
- Objects are always casted to true, GMP(0) will equal to true.
This is incorrect, GMP object do not support casts to bool
See https://3v4l.org/LHpD1
This is weird. Any PHP user would expect that a zero number can be easily
casted to boolean false. This is also why I think we need a scalar decimal
type.
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
I disagree with this part, floats can not represent decimal values
properly e.g.
$f1 = 0.1;
$f2 = 0.2;
$f3 = 0.3;
var_dump($f1 + $f2 === $f3);
will return false.
As such, floats are not at all compatible with decimals.
Yes, I know that. I mentioned that we can not convert float to decimal
without an intermediate value where we apply rounding and get a
human-readable value in some representation (string or whatsoever).
My intention was to provide implicit conversion with warnings because I
guess that the majority of end users will expect that numeric scalar types
are interoperable. To not make them scared and point them to issues in
their code instead of slapping their hands immediately when they run
something that is not so correct.
Moreover, the whole point of adding a warning when implicit conversions
from int to float happen was to be able to warn before elevating the
warning to a TypeError.
Sorry, I'm not aware of issues of converting ints to floats. Are there any
issues? I understand the issue of the backward conversion, but not the
forward one. Could you add details here?
Therefore, introducing behaviour that warns instead of throwing a
TypeError is already a no-go from my PoV.
I'm okay with TypeErrors for float to decimal conversions as well. This is
not a key point of my proposal. If the community prefers errors instead of
warnings - that's also fine.
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.This behaviour is rather suboptimal, I'd rather have literals be decimals,
as decimals to floats should always be a reasonable implicit coercion
considering we already do int to float.
Is it optimal to perform conversions on every fractional literal? This is
also not a key point for me and I'm okay with any implementation that the
internals community prefers.
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.Please no, no new declare directives that affect engine behaviour.
Strict types was already a mistake because of this IMHO.
I didn't know that the strict types directive was a mistake. My intention
is to be able to write clean all-decimal units of code and not break the
backward compatibility. The old code should work as it was before. At the
same time, there are use cases when the whole class/project should be
written with decimals only. As a user, I want to do that without complex
language structures and excessive typehints or explicit conversions. The
all-decimal code should be as clean as it is now with floats. This is why
I proposed this directive. Can you suggest something better to achieve the
same?
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.5This is a kind of "default_decimal" for a specific operation.
Again, this should return a decimal instead IMHO.
While it will work from the logical perspective, additional conversions
from decimal to float may incur performance issues in the existing code
that relies on floats only. Again, my intention here is to not break
backward compatibility and not introduce performance issues to the existing
code.
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 conversionAll 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.Messing with the return value type has already proved controversial.
And as I said previously, I am dead against having implicit float to
decimal conversions.
Making the functions type generic is something that I am fine, however.
I'm fine with generic functions as well. I'm most probably not aware of all
the ongoing discussions. If the internals community prefers generic
functions, I'm absolutely fine with it.
The new type uses libmpdec internally to perform decimal calculations
(same
as Python).I really don't think we need arbitrary precision decimals.
I'm also not convinced using a floating point spec is the most sensible,
due to the rounding errors this is going to introduce.
The non-arbitrary IEEE 754-2008 specification cannot describe the decimal
123457.1467 exactly, which is frankly pointless.Decimals, or more broadly rational numbers that, outside numerical
computations, that are going to be used are not going to need huge
denominators.I've been thinking about this for a while, and I personally think that
rational numbers are just better than trying to support only decimals.
And considering we have 64 bits to play with for a new zval type splitting
the bits into a 32-bit unsigned integer for the numerator and an 32-bit
signed integer for the denominator should provide us with enough reasonable
rational numbers.
As any number that do not fit in this structure seems to be something
relegated to the world of numerical computation where floats are what are
mostly used anyway.
If you can suggest a better way of working with fractional numbers - please
do :) But IMO limiting the language by 64 bits is not a good way. It is
more easy to go over the limit than you may think :) Especially in some
intermediary values while performing complex calculations. I could be
wrong, but fractional numbers limited to 64 bits sound like a bandaid. The
language will tell users "hey, you can do something general, but if you
want something else please don't use me at all or involve bandaids with
extensions". My key point is not only to allow working with decimals, but
also to make all the alternatives useless, cover their functionality by
builtin tools, to free users from thinking about workarounds and bandaids.
From my POV, introducing a new decimal type that does not cover the
GMP/bcmath/ext-decimal functionality is pointless and a waste of time.
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.Yes, this is a major project, and as said above I have also thought about
adding a rational number type.
But the main hurdle for this is changing the zval structure and applying
the change everywhere to the engine and extensions.
I understand that changing something that is at the core of the whole
project is a huge butthurt. But let's focus on end benefits :) I love PHP
but it is also well-known that it is perceived as a language NOT for
"serious and mature" projects. Partly because if you try working with
financial data for a bank app, for example, you immediately have troubles
choosing a workaround to work with fractional numbers and the code you
write will look weird. How quickly you would be able to read something like
this: gmp_add(gmp_mul(gmp_div(gmp_sub($value, $add), $value2, $factor,
gmp_sin($value3), gmp_intval($value))) ? This absence of intuitive
readability makes the development and support difficult and it's better to
choose another language. This is what I want to change and make PHP
powerful enough for this kind of applications.
Best regards,
Alex.
On Fri, 8 Dec 2023 at 10:14, Alexander Pravdin alex.pravdin@interi.co
wrote:
- Objects are always casted to true, GMP(0) will equal to true.
This is incorrect, GMP object do not support casts to bool
See https://3v4l.org/LHpD1This is weird. Any PHP user would expect that a zero number can be easily
casted to boolean false. This is also why I think we need a scalar decimal
type.
Internal objects can overload casts, SimpleXML overloads the boolean cast
already.
We could add this to GMP to return false for 0.
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
I disagree with this part, floats can not represent decimal values
properly e.g.
$f1 = 0.1;
$f2 = 0.2;
$f3 = 0.3;
var_dump($f1 + $f2 === $f3);will return false.
As such, floats are not at all compatible with decimals.
Yes, I know that. I mentioned that we can not convert float to decimal
without an intermediate value where we apply rounding and get a
human-readable value in some representation (string or whatsoever).My intention was to provide implicit conversion with warnings because I
guess that the majority of end users will expect that numeric scalar types
are interoperable. To not make them scared and point them to issues in
their code instead of slapping their hands immediately when they run
something that is not so correct.Moreover, the whole point of adding a warning when implicit conversions
from int to float happen was to be able to warn before elevating the
warning to a TypeError.Sorry, I'm not aware of issues of converting ints to floats. Are there any
issues? I understand the issue of the backward conversion, but not the
forward one. Could you add details here?
I meant converting floats to int.
But you can lose precision when converting an integer to float as there are
only 53 bits for the coefficient.
Therefore, introducing behaviour that warns instead of throwing a
TypeError is already a no-go from my PoV.
I'm okay with TypeErrors for float to decimal conversions as well. This is
not a key point of my proposal. If the community prefers errors instead of
warnings - that's also fine.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.This behaviour is rather suboptimal, I'd rather have literals be decimals,
as decimals to floats should always be a reasonable implicit coercion
considering we already do int to float.Is it optimal to perform conversions on every fractional literal? This is
also not a key point for me and I'm okay with any implementation that the
internals community prefers.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.Please no, no new declare directives that affect engine behaviour.
Strict types was already a mistake because of this IMHO.I didn't know that the strict types directive was a mistake. My intention
is to be able to write clean all-decimal units of code and not break the
backward compatibility. The old code should work as it was before. At the
same time, there are use cases when the whole class/project should be
written with decimals only. As a user, I want to do that without complex
language structures and excessive typehints or explicit conversions. The
all-decimal code should be as clean as it is now with floats. This is why
I proposed this directive. Can you suggest something better to achieve the
same?
The issue is that I don't think having arbitrary precision decimals as a
core language feature is a necessity compared to rational types.
A cast from rational to float wouldn't produce a large round trip, whereas
trying to figure out arbitrary precision is more difficult.
But in any case, having a declare/INI or whatever that changes the
behaviour of the engine/language is not a good design choice.
[...]
The new type uses libmpdec internally to perform decimal calculations
(same
as Python).
I really don't think we need arbitrary precision decimals.
I'm also not convinced using a floating point spec is the most sensible,
due to the rounding errors this is going to introduce.
The non-arbitrary IEEE 754-2008 specification cannot describe the decimal
123457.1467 exactly, which is frankly pointless.Decimals, or more broadly rational numbers that, outside numerical
computations, that are going to be used are not going to need huge
denominators.I've been thinking about this for a while, and I personally think that
rational numbers are just better than trying to support only decimals.
And considering we have 64 bits to play with for a new zval type
splitting the bits into a 32-bit unsigned integer for the numerator and an
32-bit signed integer for the denominator should provide us with enough
reasonable rational numbers.
As any number that do not fit in this structure seems to be something
relegated to the world of numerical computation where floats are what are
mostly used anyway.If you can suggest a better way of working with fractional numbers -
please do :) But IMO limiting the language by 64 bits is not a good way. It
is more easy to go over the limit than you may think :) Especially in some
intermediary values while performing complex calculations. I could be
wrong, but fractional numbers limited to 64 bits sound like a bandaid. The
language will tell users "hey, you can do something general, but if you
want something else please don't use me at all or involve bandaids with
extensions". My key point is not only to allow working with decimals, but
also to make all the alternatives useless, cover their functionality by
builtin tools, to free users from thinking about workarounds and bandaids.
From my POV, introducing a new decimal type that does not cover the
GMP/bcmath/ext-decimal functionality is pointless and a waste of time.
Again, the use cases for arbitrary precision numbers seems rather limited,
and having this as an extension does not seem problematic at all.
My current issue is that there is no way to represent "small" numbers such
as 0.1 or 5.8 exactly.
Arbitrary precision is a totally different ballpark, be that for integers
and/or fractional values and makes everything slow, just look at Python who
is finally investing time and money to make the most common numbers
(those that fit in a machine word) not be abysmally slow.
[...]
How quickly you would be able to read something like this:
gmp_add(gmp_mul(gmp_div(gmp_sub($value, $add), $value2, $factor,
gmp_sin($value3), gmp_intval($value))) ?
This absence of intuitive readability makes the development and support
difficult and it's better to choose another language. This is what I want
to change and make PHP powerful enough for this kind of applications.
GMP supports operator overloading, so you do not need to write this, and as
far as I see, ext/decimal also supports operator overloading so you can
just write it using the normal arithmetic operations.
Moreover, it supports type casts so you can just do (int) $GMP instead of
gmp_intval($GMP);
And it also supports using the comparisons operatos on it instead of using
gmp_cmp()
The only thing that is, currently, not possible to do is a cast to GMP
using (GMP), but maybe that's something that could be added to the engine
for internal objects, but I'm not sure about this.
It seems, your main motivation to have this as a language feature is to
have access to operator overloading and casting behaviour, that internal
objects already support.
Which diminish the cost/benefit of making the engine changes, especially if
the default behaviour doesn't change and one needs to maintain effectively
two different versions of PHP depending on if it is in "decimal" or
"floating" mode, which raises a plethora of questions just in its own.
Best regards,
Gina P. Banyard
On Fri, 8 Dec 2023 at 10:14, Alexander Pravdin alex.pravdin@interi.co
wrote:
- Objects are always casted to true, GMP(0) will equal to true.
This is incorrect, GMP object do not support casts to bool
See https://3v4l.org/LHpD1This is weird. Any PHP user would expect that a zero number can be easily
casted to boolean false. This is also why I think we need a scalar decimal
type.Internal objects can overload casts, SimpleXML overloads the boolean cast
already.
We could add this to GMP to return false for 0.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
I disagree with this part, floats can not represent decimal values
properly e.g.
$f1 = 0.1;
$f2 = 0.2;
$f3 = 0.3;
var_dump($f1 + $f2 === $f3);will return false.
As such, floats are not at all compatible with decimals.
Yes, I know that. I mentioned that we can not convert float to decimal
without an intermediate value where we apply rounding and get a
human-readable value in some representation (string or whatsoever).My intention was to provide implicit conversion with warnings because I
guess that the majority of end users will expect that numeric scalar types
are interoperable. To not make them scared and point them to issues in
their code instead of slapping their hands immediately when they run
something that is not so correct.Moreover, the whole point of adding a warning when implicit conversions
from int to float happen was to be able to warn before elevating the
warning to a TypeError.Sorry, I'm not aware of issues of converting ints to floats. Are there any
issues? I understand the issue of the backward conversion, but not the
forward one. Could you add details here?I meant converting floats to int.
But you can lose precision when converting an integer to float as there are
only 53 bits for the coefficient.Therefore, introducing behaviour that warns instead of throwing a
TypeError is already a no-go from my PoV.
I'm okay with TypeErrors for float to decimal conversions as well. This is
not a key point of my proposal. If the community prefers errors instead of
warnings - that's also fine.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.This behaviour is rather suboptimal, I'd rather have literals be decimals,
as decimals to floats should always be a reasonable implicit coercion
considering we already do int to float.Is it optimal to perform conversions on every fractional literal? This is
also not a key point for me and I'm okay with any implementation that the
internals community prefers.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.Please no, no new declare directives that affect engine behaviour.
Strict types was already a mistake because of this IMHO.I didn't know that the strict types directive was a mistake. My intention
is to be able to write clean all-decimal units of code and not break the
backward compatibility. The old code should work as it was before. At the
same time, there are use cases when the whole class/project should be
written with decimals only. As a user, I want to do that without complex
language structures and excessive typehints or explicit conversions. The
all-decimal code should be as clean as it is now with floats. This is why
I proposed this directive. Can you suggest something better to achieve the
same?The issue is that I don't think having arbitrary precision decimals as a
core language feature is a necessity compared to rational types.
A cast from rational to float wouldn't produce a large round trip, whereas
trying to figure out arbitrary precision is more difficult.
But in any case, having a declare/INI or whatever that changes the
behaviour of the engine/language is not a good design choice.[...]
The new type uses libmpdec internally to perform decimal calculations
(same
as Python).
I really don't think we need arbitrary precision decimals.
I'm also not convinced using a floating point spec is the most sensible,
due to the rounding errors this is going to introduce.
The non-arbitrary IEEE 754-2008 specification cannot describe the decimal
123457.1467 exactly, which is frankly pointless.Decimals, or more broadly rational numbers that, outside numerical
computations, that are going to be used are not going to need huge
denominators.I've been thinking about this for a while, and I personally think that
rational numbers are just better than trying to support only decimals.
And considering we have 64 bits to play with for a new zval type
splitting the bits into a 32-bit unsigned integer for the numerator and an
32-bit signed integer for the denominator should provide us with enough
reasonable rational numbers.
As any number that do not fit in this structure seems to be something
relegated to the world of numerical computation where floats are what are
mostly used anyway.If you can suggest a better way of working with fractional numbers -
please do :) But IMO limiting the language by 64 bits is not a good way. It
is more easy to go over the limit than you may think :) Especially in some
intermediary values while performing complex calculations. I could be
wrong, but fractional numbers limited to 64 bits sound like a bandaid. The
language will tell users "hey, you can do something general, but if you
want something else please don't use me at all or involve bandaids with
extensions". My key point is not only to allow working with decimals, but
also to make all the alternatives useless, cover their functionality by
builtin tools, to free users from thinking about workarounds and bandaids.
From my POV, introducing a new decimal type that does not cover the
GMP/bcmath/ext-decimal functionality is pointless and a waste of time.Again, the use cases for arbitrary precision numbers seems rather limited,
and having this as an extension does not seem problematic at all.
My current issue is that there is no way to represent "small" numbers such
as 0.1 or 5.8 exactly.
Arbitrary precision is a totally different ballpark, be that for integers
and/or fractional values and makes everything slow, just look at Python who
is finally investing time and money to make the most common numbers
(those that fit in a machine word) not be abysmally slow.[...]
How quickly you would be able to read something like this:
gmp_add(gmp_mul(gmp_div(gmp_sub($value, $add), $value2, $factor,
gmp_sin($value3), gmp_intval($value))) ?This absence of intuitive readability makes the development and support
difficult and it's better to choose another language. This is what I want
to change and make PHP powerful enough for this kind of applications.GMP supports operator overloading, so you do not need to write this, and as
far as I see, ext/decimal also supports operator overloading so you can
just write it using the normal arithmetic operations.
Moreover, it supports type casts so you can just do (int) $GMP instead of
gmp_intval($GMP);
And it also supports using the comparisons operatos on it instead of using
gmp_cmp()The only thing that is, currently, not possible to do is a cast to GMP
using (GMP), but maybe that's something that could be added to the engine
for internal objects, but I'm not sure about this.It seems, your main motivation to have this as a language feature is to
have access to operator overloading and casting behaviour, that internal
objects already support.
Which diminish the cost/benefit of making the engine changes, especially if
the default behaviour doesn't change and one needs to maintain effectively
two different versions of PHP depending on if it is in "decimal" or
"floating" mode, which raises a plethora of questions just in its own.Best regards,
Gina P. Banyard
Hey Gina,
GMP supports operator overloading
GMP kinda-sorta-most-of-the-time supports operator overloading.
Sometimes ... it doesn't. I implemented a field library in PHP (for
work a couple of years ago) and occasionally, overloading would cast
things back to float/int and break the math. I don't have access to
that code, so I don't have any examples readily available (and the
fact that an extension can do overloading but we can't in user-land is
a whole different can of worms which made this library ridiculously
hard to work with -- we rewrote everything in Scala and never looked
back). Needless to say, if I were to go into a project that required
GMP, I wouldn't trust the overloading.
Just my 2¢
Robert Landers
Software Engineer
Utrecht NL
On Tue, Dec 12, 2023 at 1:00 PM Robert Landers landers.robert@gmail.com
wrote:
Hey Gina,
GMP supports operator overloading
GMP kinda-sorta-most-of-the-time supports operator overloading.
Sometimes ... it doesn't. I implemented a field library in PHP (for
work a couple of years ago) and occasionally, overloading would cast
things back to float/int and break the math. I don't have access to
that code, so I don't have any examples readily available (and the
fact that an extension can do overloading but we can't in user-land is
a whole different can of worms which made this library ridiculously
hard to work with -- we rewrote everything in Scala and never looked
back). Needless to say, if I were to go into a project that required
GMP, I wouldn't trust the overloading.Just my 2¢
I tried not to bring that up in this discussion because it would probably
sound bitter/unhelpful, but the lack of userland operator overloading is
definitely something that makes a native decimal scalar more necessary.
Voters have repeatedly rejected userland operator overloads. The last
proposal was mine, and most of the people who voted not told me that they
didn't have any real problems with the design, they just don't like
operator overloads.
Things like ext-decimal and GMP are always going to be clunky and annoying
to use without them though. It's one of the reasons I considered writing my
own implementation of a decimal scalar for core after my operator overload
RFC was declined. The use case for a scalar decimal type is much stronger
since nothing like it can be remotely implemented in userland at all. (I
know from experience, I wrote from scratch the most comprehensive arbitrary
precision math library in PHP.)
Jordan
On Tue, 12 Dec 2023 at 21:00, Robert Landers landers.robert@gmail.com
wrote:
GMP supports operator overloading
GMP kinda-sorta-most-of-the-time supports operator overloading.
Sometimes ... it doesn't. I implemented a field library in PHP (for
work a couple of years ago) and occasionally, overloading would cast
things back to float/int and break the math. I don't have access to
that code, so I don't have any examples readily available (and the
fact that an extension can do overloading but we can't in user-land is
a whole different can of worms which made this library ridiculously
hard to work with -- we rewrote everything in Scala and never looked
back). Needless to say, if I were to go into a project that required
GMP, I wouldn't trust the overloading.
I have no idea how this is possible considering GMP will happily throw
type errors left and right even in cases when it shouldn't.
If you encountered this, you should have submitted a bug report.
Because, using a potential bug as an excuse for not doing this is... weird?
I have come around userland operator overloading with the proposal from
Jordan, but considering this hasn't passed it might take a while before
someone gives it a crack at it again.
And it has always been a thing that the engine, and internal extensions,
can do more things than userland.
Especially nonsensical stuff like variadic parameters not at the end...
Gina P. Banyard
GMP supports operator overloading
GMP kinda-sorta-most-of-the-time supports operator overloading.
Sometimes ... it doesn't. I implemented a field library in PHP (for
work a couple of years ago) and occasionally, overloading would cast
things back to float/int and break the math. I don't have access to
that code, so I don't have any examples readily available (and the
fact that an extension can do overloading but we can't in user-land is
a whole different can of worms which made this library ridiculously
hard to work with -- we rewrote everything in Scala and never looked
back). Needless to say, if I were to go into a project that required
GMP, I wouldn't trust the overloading.
Hey Gina,
I have no idea how this is possible considering GMP will happily throw type errors left and right even in cases when it shouldn't.
If you encountered this, you should have submitted a bug report.
Because, using a potential bug as an excuse for not doing this is... weird?
The issue should have been reproducible on a smaller scale, but it
wasn't, thereby hinting that we were hitting some extremely rare edge
case that may not be easily testable. The fact that we hit it,
couldn't create a simple reproduction for reporting a bug, and you
should, theoretically, be able to rely on your software to compute
math correctly made that one person's argument: "Why don't we rewrite
this in X?" that much more persuasive...
I have come around userland operator overloading with the proposal from Jordan, but considering this hasn't passed it might take a while before someone gives it a crack at it again.
And it has always been a thing that the engine, and internal extensions, can do more things than userland.
Especially nonsensical stuff like variadic parameters not at the end...
Considering operator overloading has been declined twice (in 2021:
https://wiki.php.net/rfc/user_defined_operator_overloads, and 2020
https://wiki.php.net/rfc/userspace_operator_overloading) for "reasons"
has me thinking it probably won't be proposed again. With comments
like:
it is not your implementation proposal,
but rather the concept per-se that IMO shouldn't land in the language
nobody will likely ever try this again, at least in the foreseeable
future. With comments like that, there is simply no way forward.
There's no convincing (at least via email) and by that point, it's too
late anyway, they already voted but didn't even show up for the
discussion that happened for weeks. Literally wasting everyone's time.
The only way we'd ever get something like this passed is if someone
proposes an RFC that prevents people from voting based on
political/personal reasons and restricts voting "no" to technical
reasons only; or some voters reopen one of the original RFC's for a
revote and leave it in the "Pending Implementation" section as needing
an implementation.
The fact that people can and do vote "no" for no other reason than
they "don't like it" or they "don't want to use it" leaves a bad taste
in my mouth.
Robert Landers
Software Engineer
Utrecht NL
On Fri, Dec 15, 2023 at 12:14 AM Robert Landers landers.robert@gmail.com
wrote:
nobody will likely ever try this again, at least in the foreseeable
future. With comments like that, there is simply no way forward.
There's no convincing (at least via email) and by that point, it's too
late anyway, they already voted but didn't even show up for the
discussion that happened for weeks. Literally wasting everyone's time.
The only way we'd ever get something like this passed is if someone
proposes an RFC that prevents people from voting based on
political/personal reasons and restricts voting "no" to technical
reasons only; or some voters reopen one of the original RFC's for a
revote and leave it in the "Pending Implementation" section as needing
an implementation.The fact that people can and do vote "no" for no other reason than
they "don't like it" or they "don't want to use it" leaves a bad taste
in my mouth.
Okay, so I'm probably the most affected by this, considering we're
discussing my proposal which was declined and I spent over 400 hours of
work on it prior to the vote. So please understand where I'm coming from
when I say this: I firmly believe that people should be allowed to vote no
on an RFC simply because they feel that it "doesn't belong in PHP". That is
a voter taking a position on what the language is for, how it should be
designed from a more broad perspective, and I think it's important to have
that in order to maintain a productive language that does its job well.
The main issue I have discovered through this experience is that some
people have difficulty or lack the experience necessary to separate an
opinion about the language design philosophy from what would be personally
useful to them. That is a problem, but it's a problem that is just part
of working collaboratively with humans. It's not unique to this group, to
PHP, or to this situation.
The reason, for everyone else reading, that this is relevant to the current
discussion about a scalar decimal type is that this is likely to face some
similar pushback. This email will go out to hundreds of people, but only a
few are going to reply, and most of the people currently participating in
this discussion do not have voting rights, so this RFC if it's worked on
will have to be developed without the feedback of the people who actually
decide whether it goes into the language.
A scalar decimal type will be extremely useful for certain types of
applications, foundationally critical to other types of applications, and
completely unused for others. A big part of writing this RFC would be
explaining to the people who do not work on any applications that benefit
why it would be good to include it in the language anyway.
A scalar decimal type would create a hard dependency on libmpdec, which I
also expect to be something that needs to be addressed. MPDec is licensed
under the Simplified BSD License. I think it could be bundled if needed,
but it couldn't be relicensed with the PHP License itself.
Jordan
On Fri, Dec 15, 2023 at 12:14 AM Robert Landers landers.robert@gmail.com
wrote:nobody will likely ever try this again, at least in the foreseeable
future. With comments like that, there is simply no way forward.
There's no convincing (at least via email) and by that point, it's too
late anyway, they already voted but didn't even show up for the
discussion that happened for weeks. Literally wasting everyone's time.
The only way we'd ever get something like this passed is if someone
proposes an RFC that prevents people from voting based on
political/personal reasons and restricts voting "no" to technical
reasons only; or some voters reopen one of the original RFC's for a
revote and leave it in the "Pending Implementation" section as needing
an implementation.The fact that people can and do vote "no" for no other reason than
they "don't like it" or they "don't want to use it" leaves a bad taste
in my mouth.Okay, so I'm probably the most affected by this, considering we're
discussing my proposal which was declined and I spent over 400 hours of
work on it prior to the vote. So please understand where I'm coming from
when I say this: I firmly believe that people should be allowed to vote no
on an RFC simply because they feel that it "doesn't belong in PHP". That is
a voter taking a position on what the language is for, how it should be
designed from a more broad perspective, and I think it's important to have
that in order to maintain a productive language that does its job well.The main issue I have discovered through this experience is that some
people have difficulty or lack the experience necessary to separate an
opinion about the language design philosophy from what would be personally
useful to them. That is a problem, but it's a problem that is just part
of working collaboratively with humans. It's not unique to this group, to
PHP, or to this situation.The reason, for everyone else reading, that this is relevant to the current
discussion about a scalar decimal type is that this is likely to face some
similar pushback. This email will go out to hundreds of people, but only a
few are going to reply, and most of the people currently participating in
this discussion do not have voting rights, so this RFC if it's worked on
will have to be developed without the feedback of the people who actually
decide whether it goes into the language.A scalar decimal type will be extremely useful for certain types of
applications, foundationally critical to other types of applications, and
completely unused for others. A big part of writing this RFC would be
explaining to the people who do not work on any applications that benefit
why it would be good to include it in the language anyway.A scalar decimal type would create a hard dependency on libmpdec, which I
also expect to be something that needs to be addressed. MPDec is licensed
under the Simplified BSD License. I think it could be bundled if needed,
but it couldn't be relicensed with the PHP License itself.Jordan
Jordan has highlighted good points that probably should be addressed and in
general, I think, this project will take a while, so I would settle in for
the long haul here and think things through and reach out to people who
actively work and maintain the PHP core. There have been recent instances
of people fitting in and passing things and then regretting the way it was
done (cought readonly rfc cought ).
As for the idea itself - I fully support it. A decimal type would be
invaluable in quite a lot of places and a lot of PHP apps are handling
money and other precise numbers that really would benefit from a proper
decimal type (right now in most cases people just do it with integers or
use libraries like brick/money, but that's only a subset covered).
This is really adding a new type into the engine, so this is going to be a
big undertaking and this probably will require the work to go into PHP 9
because extensions probably are going to be affected and will need to be
upgraded to handle the new type. Especially PDO and database drivers. This
is far from a small project.
--
Arvīds Godjuks
+371 26 851 664
arvids.godjuks@gmail.com
Telegram: @psihius https://t.me/psihius
On Fri, Dec 15, 2023 at 12:59 PM Arvids Godjuks arvids.godjuks@gmail.com
wrote:
On Fri, 15 Dec 2023 at 22:32, Jordan LeDoux jordan.ledoux@gmail.com
wrote:On Fri, Dec 15, 2023 at 12:14 AM Robert Landers <landers.robert@gmail.com
wrote:
nobody will likely ever try this again, at least in the foreseeable
future. With comments like that, there is simply no way forward.
There's no convincing (at least via email) and by that point, it's too
late anyway, they already voted but didn't even show up for the
discussion that happened for weeks. Literally wasting everyone's time.
The only way we'd ever get something like this passed is if someone
proposes an RFC that prevents people from voting based on
political/personal reasons and restricts voting "no" to technical
reasons only; or some voters reopen one of the original RFC's for a
revote and leave it in the "Pending Implementation" section as needing
an implementation.The fact that people can and do vote "no" for no other reason than
they "don't like it" or they "don't want to use it" leaves a bad taste
in my mouth.Okay, so I'm probably the most affected by this, considering we're
discussing my proposal which was declined and I spent over 400 hours of
work on it prior to the vote. So please understand where I'm coming from
when I say this: I firmly believe that people should be allowed to vote no
on an RFC simply because they feel that it "doesn't belong in PHP". That
is
a voter taking a position on what the language is for, how it should be
designed from a more broad perspective, and I think it's important to have
that in order to maintain a productive language that does its job well.The main issue I have discovered through this experience is that some
people have difficulty or lack the experience necessary to separate an
opinion about the language design philosophy from what would be personally
useful to them. That is a problem, but it's a problem that is just part
of working collaboratively with humans. It's not unique to this group, to
PHP, or to this situation.The reason, for everyone else reading, that this is relevant to the
current
discussion about a scalar decimal type is that this is likely to face some
similar pushback. This email will go out to hundreds of people, but only a
few are going to reply, and most of the people currently participating in
this discussion do not have voting rights, so this RFC if it's worked on
will have to be developed without the feedback of the people who actually
decide whether it goes into the language.A scalar decimal type will be extremely useful for certain types of
applications, foundationally critical to other types of applications, and
completely unused for others. A big part of writing this RFC would be
explaining to the people who do not work on any applications that benefit
why it would be good to include it in the language anyway.A scalar decimal type would create a hard dependency on libmpdec, which I
also expect to be something that needs to be addressed. MPDec is licensed
under the Simplified BSD License. I think it could be bundled if needed,
but it couldn't be relicensed with the PHP License itself.Jordan
Jordan has highlighted good points that probably should be addressed and
in general, I think, this project will take a while, so I would settle in
for the long haul here and think things through and reach out to people who
actively work and maintain the PHP core. There have been recent instances
of people fitting in and passing things and then regretting the way it was
done (cought readonly rfc cought ).As for the idea itself - I fully support it. A decimal type would be
invaluable in quite a lot of places and a lot of PHP apps are handling
money and other precise numbers that really would benefit from a proper
decimal type (right now in most cases people just do it with integers or
use libraries like brick/money, but that's only a subset covered).
This is really adding a new type into the engine, so this is going to be a
big undertaking and this probably will require the work to go into PHP 9
because extensions probably are going to be affected and will need to be
upgraded to handle the new type. Especially PDO and database drivers. This
is far from a small project.
Here's the research that I did into this subject when I was considering
making my own decimal scalar proposal about a year ago.
MPDec: This is used by ext-decimal. It's licensed with the Simplified BSD
License. It has functions for basic arithmetic (add, subtract, multiply,
divide), as well as logarithmic functions (ln, log10, exp, power, square
root). It does not have functions for trigonometric operations, and has
some limitations for where arbitrary precision numbers can be used (for
instance with pow).
GMP: This is the underlying library of several other math libraries in C.
It is dual licensed with the LGPL 3 and GPL 2 licenses. It has functions
for basic arithmetic.
MPFR: This is an extension of the GMP library that supports a much wider
variety of math. It is licensed with the LGPL. It supports arithmetic,
logarithmic, trigonometric, inverse trigonometric, hyperbolic, and number
theory operations (such as the gamma function which extends the factorial
operation beyond integers).
FLINT: This is a library built on top of MPFR (or rather, it depends on
MPFR). It is licensed with the LGPL 2. It supports everything that MPFR
does, with additional support for complex numbers, expressions (such as
polynomials and exponentials), derivatives and integrals, additional number
theory operations (such as generating Bernoulli numbers), and other
features.
I think all of these could be bundled.
MPDec will not supply all of the features that have been discussed in this
mailing list thread. The minimal library that does is probably MPFR.
A zval has to fit in 64 bits, which none of these will do, so our scalar
decimal type would probably have to be refcounted the same way that objects
are. To have it behave like other scalars would probably involve some very
tricky work implementing efficient copying into scopes. Otherwise, decimal
scalars would pass-by-pointer in the same way objects do where they ignore
function scope isolation to avoid copying large amounts of memory for
temporary scope changes. Having it behave like an int or float zval would
require either a big change to how zvals work, or something rather creative
with pointers and scope based copy-on-write with garbage collection to
clean things up.
While the arbitrary precision types would not fit in a zval, they would be
much smaller than objects are in PHP (generally), so something creative
might be worth at least exploring. I think the kind of radical change that
would be necessary for them to fit entirely in a zval instead of just
fitting the pointer in a zval are likely to be a non-starter. I would
imagine it would reduce the performance of other zval optimizations.
However, the specifics of zval implementation is something that I am not at
all an expert in, so take my research on that front with a grain of salt.
Jordan
The issue is that I don't think having arbitrary precision decimals as a
core language feature is a necessity compared to rational types.
A cast from rational to float wouldn't produce a large round trip, whereas
trying to figure out arbitrary precision is more difficult.
But in any case, having a declare/INI or whatever that changes the
behaviour of the engine/language is not a good design choice.
I don't have strong feelings about arbitrary precision decimals either way, but I do strongly agree with this point. Having the language behavior (including the precision of numbers) change with an ini setting is long-understood to be a bad idea, and we've been trying to phase it out. A decimal feature that relies on a language-affecting ini setting is just not going to fly these days, IMO, and rightly so.
I am curious, GB, if you're proposing an actual rational
type, which stores values internally as just numerator and denominator separately until some point when it renders down to a float (eg, on print)? That sounds neat, though I am nowhere near expert enough in that area to say what ugly edge cases that might run into.
--Larry Garfield
On Tue, Dec 12, 2023 at 1:26 PM Larry Garfield larry@garfieldtech.com
wrote:
The issue is that I don't think having arbitrary precision decimals as a
core language feature is a necessity compared to rational types.
A cast from rational to float wouldn't produce a large round trip,
whereas
trying to figure out arbitrary precision is more difficult.
But in any case, having a declare/INI or whatever that changes the
behaviour of the engine/language is not a good design choice.I don't have strong feelings about arbitrary precision decimals either
way, but I do strongly agree with this point. Having the language behavior
(including the precision of numbers) change with an ini setting is
long-understood to be a bad idea, and we've been trying to phase it out. A
decimal feature that relies on a language-affecting ini setting is just not
going to fly these days, IMO, and rightly so.I am curious, GB, if you're proposing an actual
rational
type, which
stores values internally as just numerator and denominator separately until
some point when it renders down to a float (eg, on print)? That sounds
neat, though I am nowhere near expert enough in that area to say what ugly
edge cases that might run into.--Larry Garfield
I agree that an INI setting or declare is wrong for this.
The ugly edge cases on a native rational type tend to be in complex
algorithms, which many libraries deal with by converting the rational to a
float or decimal for those complex algorithms and just avoiding the issue.
(Algorithms like sin()
, atan()
, or exp()
). It's not an unsolveable
problem if you really want rational types. There are libraries that do it.
But unless there's someone here volunteering, it's probably a non-starter,
as that's something I won't touch.
Jordan
Hi Jordan.
What's the name of the library you're talking about? Maybe the cons of a
core implementation can be highlighted if we can see the limitations of a
user-land approach.
Best,
Erick
Em ter., 12 de dez. de 2023 às 18:35, Jordan LeDoux jordan.ledoux@gmail.com
escreveu:
On Tue, Dec 12, 2023 at 1:26 PM Larry Garfield larry@garfieldtech.com
wrote:The issue is that I don't think having arbitrary precision decimals as
a
core language feature is a necessity compared to rational types.
A cast from rational to float wouldn't produce a large round trip,
whereas
trying to figure out arbitrary precision is more difficult.
But in any case, having a declare/INI or whatever that changes the
behaviour of the engine/language is not a good design choice.I don't have strong feelings about arbitrary precision decimals either
way, but I do strongly agree with this point. Having the language
behavior
(including the precision of numbers) change with an ini setting is
long-understood to be a bad idea, and we've been trying to phase it
out. A
decimal feature that relies on a language-affecting ini setting is just
not
going to fly these days, IMO, and rightly so.I am curious, GB, if you're proposing an actual
rational
type, which
stores values internally as just numerator and denominator separately
until
some point when it renders down to a float (eg, on print)? That sounds
neat, though I am nowhere near expert enough in that area to say what
ugly
edge cases that might run into.--Larry Garfield
I agree that an INI setting or declare is wrong for this.
The ugly edge cases on a native rational type tend to be in complex
algorithms, which many libraries deal with by converting the rational to a
float or decimal for those complex algorithms and just avoiding the issue.
(Algorithms likesin()
,atan()
, orexp()
). It's not an unsolveable
problem if you really want rational types. There are libraries that do it.
But unless there's someone here volunteering, it's probably a non-starter,
as that's something I won't touch.Jordan
Oh, I just realized that I used the wrong word, so let me rephrase that:
What's the name of the library you're talking about? Maybe the pros of a
core implementation can be highlighted if we can see the limitations of a
user-land approach.
Best,
Erick
Em ter., 12 de dez. de 2023 às 18:52, Erick de Azevedo Lima <
ericklima.comp@gmail.com> escreveu:
Hi Jordan.
What's the name of the library you're talking about? Maybe the cons of a
core implementation can be highlighted if we can see the limitations of a
user-land approach.Best,
ErickEm ter., 12 de dez. de 2023 às 18:35, Jordan LeDoux <
jordan.ledoux@gmail.com> escreveu:On Tue, Dec 12, 2023 at 1:26 PM Larry Garfield larry@garfieldtech.com
wrote:The issue is that I don't think having arbitrary precision decimals
as a
core language feature is a necessity compared to rational types.
A cast from rational to float wouldn't produce a large round trip,
whereas
trying to figure out arbitrary precision is more difficult.
But in any case, having a declare/INI or whatever that changes the
behaviour of the engine/language is not a good design choice.I don't have strong feelings about arbitrary precision decimals either
way, but I do strongly agree with this point. Having the language
behavior
(including the precision of numbers) change with an ini setting is
long-understood to be a bad idea, and we've been trying to phase it
out. A
decimal feature that relies on a language-affecting ini setting is just
not
going to fly these days, IMO, and rightly so.I am curious, GB, if you're proposing an actual
rational
type, which
stores values internally as just numerator and denominator separately
until
some point when it renders down to a float (eg, on print)? That sounds
neat, though I am nowhere near expert enough in that area to say what
ugly
edge cases that might run into.--Larry Garfield
I agree that an INI setting or declare is wrong for this.
The ugly edge cases on a native rational type tend to be in complex
algorithms, which many libraries deal with by converting the rational to a
float or decimal for those complex algorithms and just avoiding the issue.
(Algorithms likesin()
,atan()
, orexp()
). It's not an unsolveable
problem if you really want rational types. There are libraries that do it.
But unless there's someone here volunteering, it's probably a non-starter,
as that's something I won't touch.Jordan
On Tue, Dec 12, 2023 at 3:05 PM Erick de Azevedo Lima <
ericklima.comp@gmail.com> wrote:
Oh, I just realized that I used the wrong word, so let me rephrase that:
What's the name of the library you're talking about? Maybe the pros of a
core implementation can be highlighted if we can see the limitations of a
user-land approach.Best,
Erick
My library is Fermat: https://github.com/JordanRL/Fermat
Mine is the ONLY one that I'm aware of that even attempts to support
complex mathematical operations in PHP. However, there are other arbitrary
precision math libraries that are more limited in scope.
Other similar math libraries:
markrogoyski/math-php: The closest in terms of features compared to Fermat
brick/math: The most widely installed PHP math library on Packagist (due to
being a dependency of several other widely used packages). Has extremely
limited features for complex arbitrary precision functions.
MPDecimal (the underlying C library that we have discussed using here) has
extensions and functions for things like sine and cosine. Even just
exposing those would make writing algorithms for things like 3i^(2+5i)
much, much faster.
The problem is that there's no point in using arbitrary precision numbers
unless you're using the spaces after the decimal. I think we can all agree
on that. No matter how good the implementation, floats are always going to
be faster if you're unconcerned about rounding errors and such.
So if you're writing an arbitrary precision library and you want to support
the next obvious operation beyond add, subtract, multiply, and divide, you
probably naturally choose exponents. Except, calculating A^B where both A
and B have arbitrary precision and where the result will also have
arbitrary precision means that you cannot rely on things like the pow()
function. So you have to implement your own. Except implementing decimal
exponents means you really need to implement exp()
first, since all of
the efficient algorithms rely on transforming A^B into C*e^D. Except the
most efficient ways of doing that really work best if you have access to
trig functions. And ALL your calculations in the middle can't be losing
precision, otherwise there's no point in doing your own implementation, so
that means all of these functions need to have implementations as well.
This rabbit hole is so insidious that the BCMath library simple says
"fuck it" and limits you to only using integers for your exponents. That's
basically pointless though if the reason you need arbitrary precision
numbers is something like a statistics application for instance, which PHP
also lacks support for since the one extension that used to provide
statistics functions is no longer maintained and hasn't been updated since
I think 7.4.
If we get scalar decimals that only support add, subtract, multiply, and
divide, that's a huge improvement. But if we can take this opportunity to
tackle at least the trig functions as well, that will enable a LOT of new
userland code that is very messy and very slow right now.
Jordan
On Tue, Dec 12, 2023 at 4:29 PM Jordan LeDoux jordan.ledoux@gmail.com
wrote:
On Tue, Dec 12, 2023 at 3:05 PM Erick de Azevedo Lima <
ericklima.comp@gmail.com> wrote:Oh, I just realized that I used the wrong word, so let me rephrase that:
What's the name of the library you're talking about? Maybe the pros of a
core implementation can be highlighted if we can see the limitations of a
user-land approach.Best,
ErickMy library is Fermat: https://github.com/JordanRL/Fermat
Mine is the ONLY one that I'm aware of that even attempts to support
complex mathematical operations in PHP. However, there are other arbitrary
precision math libraries that are more limited in scope.Other similar math libraries:
markrogoyski/math-php: The closest in terms of features compared to Fermat
brick/math: The most widely installed PHP math library on Packagist (due
to being a dependency of several other widely used packages). Has extremely
limited features for complex arbitrary precision functions.MPDecimal (the underlying C library that we have discussed using here) has
extensions and functions for things like sine and cosine. Even just
exposing those would make writing algorithms for things like 3i^(2+5i)
much, much faster.The problem is that there's no point in using arbitrary precision numbers
unless you're using the spaces after the decimal. I think we can all agree
on that. No matter how good the implementation, floats are always going to
be faster if you're unconcerned about rounding errors and such.So if you're writing an arbitrary precision library and you want to
support the next obvious operation beyond add, subtract, multiply, and
divide, you probably naturally choose exponents. Except, calculating A^B
where both A and B have arbitrary precision and where the result will
also have arbitrary precision means that you cannot rely on things like
thepow()
function. So you have to implement your own. Except
implementing decimal exponents means you really need to implementexp()
first, since all of the efficient algorithms rely on transforming A^B into
C*e^D. Except the most efficient ways of doing that really work best if
you have access to trig functions. And ALL your calculations in the
middle can't be losing precision, otherwise there's no point in doing your
own implementation, so that means all of these functions need to have
implementations as well.This rabbit hole is so insidious that the BCMath library simple says
"fuck it" and limits you to only using integers for your exponents. That's
basically pointless though if the reason you need arbitrary precision
numbers is something like a statistics application for instance, which PHP
also lacks support for since the one extension that used to provide
statistics functions is no longer maintained and hasn't been updated since
I think 7.4.If we get scalar decimals that only support add, subtract, multiply, and
divide, that's a huge improvement. But if we can take this opportunity to
tackle at least the trig functions as well, that will enable a LOT of new
userland code that is very messy and very slow right now.Jordan
Apologies, I just realized that you were asking about the libraries that
implemented rationals with complex operations. I don't have THOSE libraries
handy unfortunately. Even my library converts to decimal first.
Jordan
Sorry for the so late reply, but I would like to continue the
discussion on this topic.
I didn't know that the strict types directive was a mistake. My intention is to be able to write clean all-decimal units of code and not break the backward compatibility. The old code should work as it was before. At the same time, there are use cases when the whole class/project should be written with decimals only. As a user, I want to do that without complex language structures and excessive typehints or explicit conversions. The all-decimal code should be as clean as it is now with floats. This is why I proposed this directive. Can you suggest something better to achieve the same?
The issue is that I don't think having arbitrary precision decimals as a core language feature is a necessity compared to rational types.
A cast from rational to float wouldn't produce a large round trip, whereas trying to figure out arbitrary precision is more difficult.
But in any case, having a declare/INI or whatever that changes the behaviour of the engine/language is not a good design choice.
How can we introduce the ability to write user code in default
decimals and at the same time keep the old way of working as it was
before, to not introduce any troubles into the existing code and not
introduce performance issues? As a user, I would like to have a
choice. When I need speed and I don't care about rounding errors, I
will use default floats. When I need precise calculations and can
sacrifice some performance, I will use default decimals. And I would
like to be able to switch between these modes selectively, not
app-wide. Just give me a choice and inform me in the documentation
about the differences and pros/cons of both types.
I'm not in the context of the core team plans regarding "strict
types". Could you share some details here? What is the current plan
regarding it? To make strict types on by default eventually? Or
something else?
If you can suggest a better way of working with fractional numbers - please do :) But IMO limiting the language by 64 bits is not a good way. It is more easy to go over the limit than you may think :) Especially in some intermediary values while performing complex calculations. I could be wrong, but fractional numbers limited to 64 bits sound like a bandaid. The language will tell users "hey, you can do something general, but if you want something else please don't use me at all or involve bandaids with extensions". My key point is not only to allow working with decimals, but also to make all the alternatives useless, cover their functionality by builtin tools, to free users from thinking about workarounds and bandaids. From my POV, introducing a new decimal type that does not cover the GMP/bcmath/ext-decimal functionality is pointless and a waste of time.
Again, the use cases for arbitrary precision numbers seems rather limited, and having this as an extension does not seem problematic at all.
My current issue is that there is no way to represent "small" numbers such as 0.1 or 5.8 exactly.
Arbitrary precision is a totally different ballpark, be that for integers and/or fractional values and makes everything slow, just look at Python who is finally investing time and money to make the most common numbers (those that fit in a machine word) not be abysmally slow.
Honestly speaking, my current pain point is that I need to work with
precise money calculations in business automation and accounting
fields. I would like to see a good tool in the PHP language to do
that. As an engineer, I understand that it should be fast enough. I
suggested mpdecimal as a good enough option. If you could suggest
something else that would fit the majority of user needs, I'm totally
for it.
How quickly you would be able to read something like this: gmp_add(gmp_mul(gmp_div(gmp_sub($value, $add), $value2, $factor, gmp_sin($value3), gmp_intval($value))) ?
This absence of intuitive readability makes the development and support difficult and it's better to choose another language. This is what I want to change and make PHP powerful enough for this kind of applications.
GMP supports operator overloading, so you do not need to write this, and as far as I see, ext/decimal also supports operator overloading so you can just write it using the normal arithmetic operations.
Moreover, it supports type casts so you can just do (int) $GMP instead of gmp_intval($GMP);
And it also supports using the comparisons operatos on it instead of using gmp_cmp()The only thing that is, currently, not possible to do is a cast to GMP using (GMP), but maybe that's something that could be added to the engine for internal objects, but I'm not sure about this.
If GMP can be converted to a scalar builtin language type, that also
will be totally fine from my point of view.
It seems, your main motivation to have this as a language feature is to have access to operator overloading and casting behaviour, that internal objects already support.
My points are the following:
- Work with scalar numeric types when I work with numbers, not with
objects or strings mimicking numbers. I want (bool)Decimal(0) to be
equal to false, not to true because non-null objects are always true. - Have it as a built-in language feature allowing anyone to use it
without requiring extension installation. - Keep backward compatibility for the code that already works with
floats while introducing an option to use only the new type when
performing math operations on numbers. And being able to have two
versions of the code working in the same app. - IDEs and static analyzers sometimes go crazy when I do math
operations or comparisons on objects.
Which diminish the cost/benefit of making the engine changes, especially if the default behaviour doesn't change and one needs to maintain effectively two different versions of PHP depending on if it is in "decimal" or "floating" mode, which raises a plethora of questions just in its own.
As a developer, I don't see an issue with having two fractional
numeric types in the language. My old code with floats is working as
usual and doesn't break. If I want to introduce precise calculations
for money/accounting/whatever using decimals, I change my code and I'm
fully responsible for its maintenance. PHP has warnings to inform
developers if they do something wrong and this is what can be used to
prevent errors in the code. The only problem here that I foresee is
the division on two ints (and some other operations I think) should
return some specific type (float or decimal) and the language needs a
mechanism to determine the intention and return the proper result
type. This is why I suggested using "declare". In other cases, if at
least one of the operands is decimal we can always return decimals.
I'm not in the context of the core team plans regarding "strict
types". Could you share some details here? What is the current plan
regarding it? To make strict types on by default eventually? Or
something else?
PHP doesn't really have a defined "core team". There are contributors
who are particularly active at a given time (sometimes, but far from
always, because someone is paying them), contributors who are
particularly long-standing and respected, contributors who throw
themselves into a pet project and make it happen, and so on.
Partly as a consequence of this, it's often hard to pin down any
long-term plan about anything, outside of what particular people would
like to see. So Gina's opinion (it was suffixed "IMHO") that strict
types was a mistake shouldn't be read as "we have a solid plan for what
is going to replace strict_types which everyone is on board with".
I think a reasonable number of people do share the sentiment that having
two separate modes was a mistake; and neither mode is actually perfect.
It's not about "making it on by default", it's about coming up with a
unified behaviour that makes the setting redundant.
All of which is something of a diversion from the topic at hand, which
is this:
How can we introduce the ability to write user code in default
decimals and at the same time keep the old way of working as it was
before, to not introduce any troubles into the existing code and not
introduce performance issues? As a user, I would like to have a
choice.
I don't think choice is really what you want: if you were designing a
language from scratch, I doubt you would say "let's give the user a
choice of what type 1 / 10 returns". What it's actually about is
backwards compatibility: what will happen to code that expects 1/10 to
give a float, if it suddenly starts giving a decimal.
For most cases, I think the rule can be as simple as "decimal in means
decimal out". What's maybe not as obvious at first sight is that that
can apply to operators as functions, and already does: 100 / 10 gives
int(10), but 100.0 / 10 gives float(10.0), as do 100 / 10.0 and 100.0
/ 10.0
By the same logic, decimal(1) / 10 can produce decimal(0.1) instead of
float(0.1), and we don't need any fancy directives. Even better if we
can introduce a shorter syntax for decimal literals, so that it becomes
1_d / 10
Where things get more complicated is with fixed-precision decimals,
which is what is generally wanted for something like money. What is the
correct result of decimal(1.03, precision: 2) / 2 - decimal(0.515, 3)?
decimal(0.51, 2)? decimal (0.52, 2)? an error? And what about
decimal(10) / 3?
If you stick to functions / methods, this is slightly less of an issue,
because you can have decimal(1.03, 2)->dividedBy(2, RoundingMode::DOWN)
== decimal(0.51, 2); or decimal(1.03, 2)->split(2) == [ decimal(0.52,
2), decimal(0.51, 2) ] Example names taken directly from the brick/money
package.
At that point, backwards compatibility is less of an issue as well: make
the new functions convenient to use, but distinct from the existing ones.
In short, the best way of avoiding declare() directives is not to
replace them with something else, but to choose a design where nobody
feels the need for them.
Regards,
--
Rowan Tommins
[IMSoP]
On Mon, Mar 18, 2024 at 1:35 PM Rowan Tommins [IMSoP] imsop.php@rwec.co.uk
wrote:
Where things get more complicated is with fixed-precision decimals,
which is what is generally wanted for something like money. What is the
correct result of decimal(1.03, precision: 2) / 2 - decimal(0.515, 3)?
decimal(0.51, 2)? decimal (0.52, 2)? an error? And what about decimal(10) /
3?If you stick to functions / methods, this is slightly less of an issue,
because you can have decimal(1.03, 2)->dividedBy(2, RoundingMode::DOWN) ==
decimal(0.51, 2); or decimal(1.03, 2)->split(2) == [ decimal(0.52, 2),
decimal(0.51, 2) ] Example names taken directly from the brick/money
package.At that point, backwards compatibility is less of an issue as well: make
the new functions convenient to use, but distinct from the existing ones
I came back to this discussion after my last reply on the BCMath as an
object thread, because we went through a very similar discussion there with
regard to operators.
I think that, roughly, the fixed precision should be defined on the scalar
itself:
1.234_d3
1.234 is the value, _d makes it a decimal type, and 3 shows that this value
has a precision of 3. (Actually, it has a SCALE of 3, since precision is
significant digits, and also includes the integer portion). But when it
comes to fixed-precision values, it should follow rules very similar to
those we discussed in the BCMath thread:
- Addition and subtraction should return a value that is the largest
scale/precision of any operands in the calculation. - Division and multiplication should return a value that is the sum of the
scale/precision of any operands + 2 or a default (perhaps configurable)
value if the sum is small, to ensure that rounding occurs correctly. Near
zero, floats have about 12-ish decimal digits of accuracy, and will return
their full accuracy for example. - Pow is extremely difficult to work with if you allow a decimal in the
exponent. The libraries we have discussed previously handle this
difficulty, but the precision needed often depends on what the calculation
is for, so I'm not entirely sure what the best way to handle this with
operators would be. Minimally, probably the same as division and
multiplication.
But if we have scalar decimal types, we need something like that _d12 to
denote a decimal scalar with a scale/precision of 12.
Jordan
But when it comes to fixed-precision values, it should follow rules very
similar to those we discussed in the BCMath thread:
- Addition and subtraction should return a value that is the largest
scale/precision of any operands in the calculation.- Division and multiplication should return a value that is the sum of
the scale/precision of any operands + 2 or a default (perhaps
configurable) value if the sum is small, to ensure that rounding occurs
correctly. Near zero, floats have about 12-ish decimal digits of
accuracy, and will return their full accuracy for example.
I haven't followed the discussion in the other thread, but I'm not sure
what the use case would be for a "fixed scale decimal" that followed
those rules.
As mentioned before, the use case I've encountered is money
calculations, where what people want to fix is the smallest unit of
account - e.g. €0.01 for practical currency, or €0.0001 for detailed
accounting / trading.
If I write $total = 1.03_d2; $perPerson = $total / 2; I want a result of
0.51_d2 or 0.52_d2 - that's why I specified a scale of 2 in the first place.
If I want an accurate result of 0.515_d3, I would just specify 1.03_d,
since the scale hasn't had any effect on the result.
If I want a lossless split into [0.51_d2, 0.52_d2] I still need a
function to exist somewhere, whether you spell that $total->split(2), or
decimal_split($total, 2), etc. So it seems safer to also have
$total->div(2, Round::DOWN) or decimal_div($total, 2, Round::DOWN) and
have $total / 2 give an error.
Possibly, it could only error if the result doesn't fit in the scale, so
that this would be fine: $total = 11.00_d2; $perPerson = $total / 2;
assert($perPerson === 5.50_d2)
Or possibly, it would just be an error to perform division on a fixed
scale decimal, but allowed on a variable-fixed scale decimal.
Regards,
Rowan Tommins
[IMSoP]
On Thu, Apr 4, 2024 at 2:28 PM Rowan Tommins [IMSoP] imsop.php@rwec.co.uk
wrote:
I haven't followed the discussion in the other thread, but I'm not sure
what the use case would be for a "fixed scale decimal" that followed
those rules.As mentioned before, the use case I've encountered is money
calculations, where what people want to fix is the smallest unit of
account - e.g. €0.01 for practical currency, or €0.0001 for detailed
accounting / trading.If I write $total = 1.03_d2; $perPerson = $total / 2; I want a result of
0.51_d2 or 0.52_d2 - that's why I specified a scale of 2 in the first
place.
Well, firstly most of the financial applications that I've worked in (I
work for a firm that writes accounting software right now) do not calculate
intermediate steps like this with fixed precision, or even have an interest
in doing so. They generally want maximum precision that is computationally
reasonable, and then round it according to their preferred method at the
end. Doing the calculations as you are suggesting would introduce a maximum
error of several cents/pence/etc. after only two or three calculations,
which is utterly useless for most applications that deal with money.
Truly "fixed-precision" is not something that decimal should even try to
be, in my opinion. The use cases where you CANNOT simply round the result
at the end to fit your display output or your storage location are very
minimal.
If I want an accurate result of 0.515_d3, I would just specify 1.03_d,
since the scale hasn't had any effect on the result.If I want a lossless split into [0.51_d2, 0.52_d2] I still need a
function to exist somewhere, whether you spell that $total->split(2), or
decimal_split($total, 2), etc. So it seems safer to also have
$total->div(2, Round::DOWN) or decimal_div($total, 2, Round::DOWN) and
have $total / 2 give an error.
I mean, what you are describing is how such OBJECTS are designed in other
languages like Python, but not scalars. Python also has (almost completely
unrestricted) userland operator overloading, which PHP does not, which
further makes the comparison a little murky.
This kind of precision restriction isn't something you would place on an
individual value, it's something you would place on all calculations.
That's why in Python this is done with a global runtime setting using
getContext().perc
and getContext().rounding
. A particular value having
a precision of X doesn't imply anything concrete about a calculation that
uses that value necessarily.
Possibly, it could only error if the result doesn't fit in the scale, so
that this would be fine: $total = 11.00_d2; $perPerson = $total / 2;
assert($perPerson === 5.50_d2)Or possibly, it would just be an error to perform division on a fixed
scale decimal, but allowed on a variable-fixed scale decimal.
Maybe we're just not understanding each other. Are you opposed to the idea
of doing this as a scalar? It certainly feels that way. This feels more
like an argument in favor of doing object-like numerics for this. I suppose
that isn't really that strange either, because as I noted such a value will
almost certainly need to be refcounted because it will not fit into the 64
bits available for a zval no matter what underlying library is used to
perform the math.
Jordan
Well, firstly most of the financial applications that I've worked in
(I work for a firm that writes accounting software right now) do not
calculate intermediate steps like this with fixed precision, or even
have an interest in doing so.
My background is in e-commerce (specifically, travel) rather than
finance. In that context, it's common to have a single-step operation
like "per_person_price equals total_price divided by number of
passengers" where both per_person_price and total_price are going to be
expressed with the same accuracy.
The top two results for "money" on Packagist are
https://www.moneyphp.org/ and https://github.com/brick/money both of
which take this general model: the scale of values is fixed, and every
operation that might produce fractions of that requires a rounding mode.
Truly "fixed-precision" is not something that decimal should even try
to be, in my opinion. The use cases where you CANNOT simply round the
result at the end to fit your display output or your storage location
are very minimal.
In that case, why do we need to think about the scale or precision of a
decimal at all? What would the syntax 1.234_d3 do differently from 1.234_d?
I mean, what you are describing is how such OBJECTS are designed in
other languages like Python, but not scalars.
I don't see any connection at all between what I'm describing and
objects. I'm talking about what operations make sense on a particular
data type, regardless of how that data type is implemented.
To be honest, I'm not really sure what "scalar" means in this context.
In PHP, we call strings "scalars" because they're neither "arrays" nor
"objects"; but none of those have definitions which are universal to
other languages.
This kind of precision restriction isn't something you would place on
an individual value, it's something you would place on all
calculations. That's why in Python this is done with a global runtime
setting usinggetContext().perc
andgetContext().rounding
. A
particular value having a precision of X doesn't imply anything
concrete about a calculation that uses that value necessarily.
Global settings avoid needing extra parameters to each operation, but
don't really work for the use case I'm describing: different currencies
have different "natural" scale, e.g. Japanese Yen have a scale of 0 (no
fractional Yen), Bitcoin has a scale of 8 (100 million satoshis in 1
bitcoin). A program dealing with multiple currencies will want to
assign a different scale to different values.
Maybe we're just not understanding each other. Are you opposed to the
idea of doing this as a scalar?
Not at all; my first examples used method syntax, because I was basing
them on https://github.com/brick/money In my last e-mail, I also gave
examples using normal function syntax.
What I'm saying is that $x / 2 doesn't have a good answer if $x is a
fixed-precision number which can't be divided by 2 without exceeding
that precision. You need a third operand, the rounding mode, so you
can't write it as a binary operator, and need some kind of function like
decimal_div(decimal $dividend, int|decimal $divisor, RoundingMode
$roundingMode). How you implement "decimal" doesn't change that at all.
Regards,
--
Rowan Tommins
[IMSoP]
Hi,
What I'm saying is that $x / 2 doesn't have a good answer if $x is a fixed-precision number which can't be divided by 2 without exceeding that precision. You need a third operand, the rounding mode, so you can't write it as a binary operator, and need some kind of function like decimal_div(decimal $dividend, int|decimal $divisor, RoundingMode $roundingMode). How you implement "decimal" doesn't change that at all.
IMHO, if need a parameter that is anything other than a pure value, it's a bad fit for a native type. It's just a different notation for creating instances.
If we really wanted decimal to be a native type, then the rounding mode and scale behavior should be completely fixed and not user selectable. If not, decimal should be implemented as a class. In fact, php's float will round values that are too fine, and cannot specify how it will round them. (This involves CPU floating point modes, but I wanted to keep it consistent so I just gave it as an example.)
Regards.
Saki
If we really wanted decimal to be a native type, then the rounding
mode and scale behavior should be completely fixed and not user
selectable. If not, decimal should be implemented as a class.
As I replied to Jordan, I don't see why this is connected to "scalar" vs
"object" types at all. An object - particularly an immutable one - is
just a way of declaring a type, and some syntax for operations on that
type. There's really no difference at all between these:
$half = $whole / 2;
$half = numeric_div($whole, 2);
$half = $whole->div(2);
In PHP, right now, the last one is only available on objects, but there
have been proposals in the past to change that; it's just syntax.
For rounding, the first one is the real problem, because there's nowhere
to put an extra operand. That problem is the same for a class with an
overloaded "/" operator, and a "scalar" type which has a definition of
"/" in the engine.
Maybe it feels more obvious that an object can carry extra state in
private properties, but internal types don't actually need private
properties at all. PHP's "array" type has a bunch of different state
without being an object (a linked list of items, a hashtable for random
access, an iteration pointer, etc); and SimpleXMLElement and DOMNode are
exposed in PHP as separate classes, but actually store state in the same
C struct provided by libxml2.
So I see it just as an API design decision: do we specify the rounding
mode of numeric division a) on every operation; b) on every value; c) in
a runtime setting (ini_set); d) in a lexically scoped setting (declare)?
My vote is for (a), maybe with (d) as a fallback.
Regards,
--
Rowan Tommins
[IMSoP]
Hi Rowan
As I replied to Jordan, I don't see why this is connected to "scalar" vs "object" types at all. An object - particularly an immutable one - is just a way of declaring a type, and some syntax for operations on that type. There's really no difference at all between these:
$half = $whole / 2;
$half = numeric_div($whole, 2);
$half = $whole->div(2);In PHP, right now, the last one is only available on objects, but there have been proposals in the past to change that; it's just syntax.
For rounding, the first one is the real problem, because there's nowhere to put an extra operand. That problem is the same for a class with an overloaded "/" operator, and a "scalar" type which has a definition of "/" in the engine.
Maybe it feels more obvious that an object can carry extra state in private properties, but internal types don't actually need private properties at all. PHP's "array" type has a bunch of different state without being an object (a linked list of items, a hashtable for random access, an iteration pointer, etc); and SimpleXMLElement and DOMNode are exposed in PHP as separate classes, but actually store state in the same C struct provided by libxml2.
So I see it just as an API design decision: do we specify the rounding mode of numeric division a) on every operation; b) on every value; c) in a runtime setting (ini_set); d) in a lexically scoped setting (declare)?
My vote is for (a), maybe with (d) as a fallback.
There is no difference in how the data is held. Agree. What I'm referring to is how to initialize the value. If the user has to specify some parameters, or can specify them as options, that is already the behavior of the object.
I'm making these opinions from an API design perspective. How the data is held internally is irrelevant. zval has a lot of data other than values. What I want to say is that if multiple types of parameters are required for initialization, they may only serve as a substitute for object for the user.
Regards.
Saki
I'm making these opinions from an API design perspective. How the data is held internally is irrelevant. zval has a lot of data other than values. What I want to say is that if multiple types of parameters are required for initialization, they may only serve as a substitute for object for the user.
Again, that only seems related to objects because that's what you're used to in PHP, and even then you're overlooking an obvious exception: array(1, 2)
If we ever do want to make decimals a native type, we would need some way to initialise a decimal value, since 1.2 will initialise a float. One of the most obvious options is a function-like syntax, decimal(1.2). If we do want numbers to carry extra information in each value, it will be no problem at all to support that.
On the other side, just because something's easy doesn't mean it's the right solution. We could make an object which contained a number and an operation, and write this:
$a = new NumberOp(42, 'add');
$b = $a->exec(15);
$c = $b->withOperation('mul');
$d = $c->exec(2);
I'm sure you'd agree that would be a bad design.
So, again, I urge you to forget about it being easy to stick an extra property on an object, and think in the abstract: does it make sense to say "this number has a preferred rounding mode", rather than "this operation has a preferred rounding mode".
Regards,
Rowan Tommins
[IMSoP]
Hi Rowan,
Again, that only seems related to objects because that's what you're used to in PHP, and even then you're overlooking an obvious exception: array(1, 2)
Arrays are a bad example here because their contents are themselves values. For example, it would make sense as an example if the third value in the array was a flag that controlled the mode in which array_merge operates, but in reality it is not.
If we ever do want to make decimals a native type, we would need some way to initialise a decimal value, since 1.2 will initialise a float. One of the most obvious options is a function-like syntax, decimal(1.2). If we do want numbers to carry extra information in each value, it will be no problem at all to support that.
I don't see any point in "scalar types" that feel almost like objects, because it just feels like you're manipulating objects with procedural functions. Why not just use objects instead?
On the other side, just because something's easy doesn't mean it's the right solution. We could make an object which contained a number and an operation, and write this:
$a = new NumberOp(42, 'add');
$b = $a->exec(15);
$c = $b->withOperation('mul');
$d = $c->exec(2);I'm sure you'd agree that would be a bad design.
Yeah, I agree that this is a terrible example. But it's easy to come up with terrible examples like this. Shouldn't you use the original decimal class as an example for comparison?
So, again, I urge you to forget about it being easy to stick an extra property on an object, and think in the abstract: does it make sense to say "this number has a preferred rounding mode", rather than "this operation has a preferred rounding mode".
Sorry, but I have no idea what you mean by "numbers have rounding modes". Numbers are just numbers, and if there's something other than numbers in there, then to me it's an object.
As you can see from the discussion so far, my idea of what a native type is and your idea of what a native type is are probably different.
At least between the two of us, we may need to first have a discussion about what the native type should be.
Regards.
Saki
I don't see any point in "scalar types" that feel almost like objects, because it just feels like you're manipulating objects with procedural functions. Why not just use objects instead?
Again, I don't think "has more than one attribute" is the same as "feel almost like objects". But we're just getting further away from the current discussion, I think.
Sorry, but I have no idea what you mean by "numbers have rounding modes". Numbers are just numbers, and if there's something other than numbers in there, then to me it's an object.
The proposed class is called BCMath\Number, which implies that every instance of that class represents a number, just as every instance of a class called DateTime represents a date and time.
In the end, a class is just a type definition. In pure OOP, it defines the type by its behaviour (methods / messages); in practice, it also defines the properties that each value of the type needs.
So I am saying that if you were designing a class to represent numbers, you would start by saying "what properties does every number value have?" I don't think "rounding mode" would be on that list, so I don't think it belongs on a class called Number.
Regards,
Rowan Tommins
[IMSoP]
Hi Rowan,
Again, I don't think "has more than one attribute" is the same as "feel almost like objects". But we're just getting further away from the current discussion, I think.
The proposed class is called BCMath\Number, which implies that every instance of that class represents a number, just as every instance of a class called DateTime represents a date and time.
In the end, a class is just a type definition. In pure OOP, it defines the type by its behaviour (methods / messages); in practice, it also defines the properties that each value of the type needs.
So I am saying that if you were designing a class to represent numbers, you would start by saying "what properties does every number value have?" I don't think "rounding mode" would be on that list, so I don't think it belongs on a class called Number.
We should cool down for now. At the very least, this is the place to discuss Alex's proposal, not to comment on my RFC.
If a deeper discussion is needed, we can do that in a new thread.
Regards.
Saki
On Mon, Apr 8, 2024 at 5:06 PM Rowan Tommins [IMSoP]
imsop.php@rwec.co.uk wrote:
If we ever do want to make decimals a native type, we would need some way to initialise a decimal value, since 1.2 will initialise a float.
My original suggestion was:
- Add "default decimal" mode with the "declare" statement in the
beginning of the file. With this statement, all decimal literals like
1.2 will compile to decimals internally, not to floats. - Add "(decimal) 1.2" typecast that will compile to a decimal number
representation when there's no "default decimal" statement in the
file.
So users will be able to switch contexts explicitly and always know
what they're dealing with.
--
Best, Alexander.
On Mon, Mar 18, 2024 at 1:35 PM Rowan Tommins [IMSoP] imsop.php@rwec.co.uk
wrote:Where things get more complicated is with fixed-precision decimals,
which is what is generally wanted for something like money. What is the
correct result of decimal(1.03, precision: 2) / 2 - decimal(0.515, 3)?
decimal(0.51, 2)? decimal (0.52, 2)? an error? And what about decimal(10) /
3?If you stick to functions / methods, this is slightly less of an issue,
because you can have decimal(1.03, 2)->dividedBy(2, RoundingMode::DOWN) ==
decimal(0.51, 2); or decimal(1.03, 2)->split(2) == [ decimal(0.52, 2),
decimal(0.51, 2) ] Example names taken directly from the brick/money
package.At that point, backwards compatibility is less of an issue as well: make
the new functions convenient to use, but distinct from the existing onesI came back to this discussion after my last reply on the BCMath as an
object thread, because we went through a very similar discussion there with
regard to operators.I think that, roughly, the fixed precision should be defined on the scalar
itself:1.234_d3
1.234 is the value, _d makes it a decimal type, and 3 shows that this value
has a precision of 3. (Actually, it has a SCALE of 3, since precision is
significant digits, and also includes the integer portion). But when it
comes to fixed-precision values, it should follow rules very similar to
those we discussed in the BCMath thread:
Woound't it be much better to capture this complex information in an
Object, and then allow operators on this? Like... Saki was pretty much
suggesting with her BCMatch/Number class RFC?
I can't imagine people looking at code understanding a syntax like this
right away, let alone of what it all means.
cheers,
Derick
--
https://derickrethans.nl | https://xdebug.org | https://dram.io
Author of Xdebug. Like it? Consider supporting me: https://xdebug.org/support
mastodon: @derickr@phpc.social @xdebug@phpc.social
On Tue, Mar 19, 2024 at 5:35 AM Rowan Tommins [IMSoP]
imsop.php@rwec.co.uk wrote:
I think a reasonable number of people do share the sentiment that having two separate modes was a mistake; and neither mode is actually perfect. It's not about "making it on by default", it's about coming up with a unified behaviour that makes the setting redundant.
This is off-topic, but just to conclude: as a user, I personally would
love to see strict_types to be always enabled eventually.
I don't think choice is really what you want: if you were designing a language from scratch, I doubt you would say "let's give the user a choice of what type 1 / 10 returns". What it's actually about is backwards compatibility: what will happen to code that expects 1/10 to give a float, if it suddenly starts giving a decimal.
In short, the best way of avoiding declare() directives is not to replace them with something else, but to choose a design where nobody feels the need for them.
I understand your point and it is completely valid. But I'm speaking
from a different angle. There are obviously two opposite areas of
calculations, two "modes": "I don't care much about the performance,
give me precise results" and "I don't care about rounding errors, give
me the fastest results". I can't imagine a way of joining them and
unifying the work with them until a completely new CPU architecture is
invented. It makes me think "how can we make both types of users
happy"? I would like to see native and intuitive support for both of
the "modes". So "declare(default_decimal)" seems to be a good option.
Users already know what is "declare" so it will be not a problem to
start using another option. Again, I'm not trying to replace float
calculations with decimals due to performance and backward
compatibility. My goal is to invent a way to make two "modes"
convenient without bandaids.
For most cases, I think the rule can be as simple as "decimal in means decimal out". What's maybe not as obvious at first sight is that that can apply to operators as functions, and already does: 100 / 10 gives int(10), but 100.0 / 10 gives float(10.0), as do 100 / 10.0 and 100.0 / 10.0
I agree that if one of the operands is decimal then the result should
be decimal. The "declare(default_decimal)" is suggested specifically
for the cases when it is unclear what result we should return and to
not introduce monstrous syntax with a lot of typecast or special
syntax.
By the same logic, decimal(1) / 10 can produce decimal(0.1) instead of float(0.1), and we don't need any fancy directives. Even better if we can introduce a shorter syntax for decimal literals, so that it becomes 1_d / 10
With "declare(default_decimal)" there's no need to introduce new
syntax for numbers. I personally would prefer to work with "normal"
number literals and let PHP decide (under my guidance) how to
represent them internally - as floats or as decimals.
Where things get more complicated is with fixed-precision decimals, which is what is generally wanted for something like money. What is the correct result of decimal(1.03, precision: 2) / 2 - decimal(0.515, 3)? decimal(0.51, 2)? decimal (0.52, 2)? an error? And what about decimal(10) / 3?
As a user, I see it as we store a decimal number internally with the
maximum number of fractional digits and let the user choose how to
represent it only when converting it from decimal to something else.
Only at this moment, some rounding will happen and we may give users
the proper tools for this. When it comes to 1/3, we may hardcode some
specific rounding mode when it exceeds the internal limit of
fractional digits. In my view, this limit will be more than enough for
99.9% of so they will be able to apply their own rounding mode to 0 or
2 or 10 fractional digits with no issues.
If you stick to functions / methods, this is slightly less of an issue, because you can have decimal(1.03, 2)->dividedBy(2, RoundingMode::DOWN) == decimal(0.51, 2); or decimal(1.03, 2)->split(2) == [ decimal(0.52, 2), decimal(0.51, 2) ] Example names taken directly from the brick/money package.
Yes, but the whole point of my suggestion is to go away from the
object syntax :) My goal is not to make the future implementation
simpler but to lift the PHP language to the next level even if will
cost much more effort in total.
And just to clarify. I'm not a big expert in decimal calculations, my
proposal is still not so specific and I may have a lack of knowledge
in some terms or details and may need help from more experienced
people. I'm discussing possible ways of the implementation and I would
like to help the community to develop some design that will make
future development aligned with some principles and eventually achieve
the final goal. I understand the complexity of the proposal so I'm not
standing on the implementation as a whole by some PHP version. But I
would be more than happy to know that the language is evolving in the
defined direction.
--
Best, Alexander
Ext-decimal:
...
- Workaround: implements the Decimal class that allows basic regular math
operations.
I just found out that under PHP 8.3, basic arithmetic operations on the
Decimal object variables are no longer supported and cause TypeError. So
this is one more point to implementing native decimals in PHP.
Best,
Alex
Gina P. Banyard
On Tue, 12 Dec 2023 at 08:51, Alexander Pravdin alex.pravdin@interi.co
wrote:
I just found out that under PHP 8.3, basic arithmetic operations on the
Decimal object variables are no longer supported and cause TypeError. So
this is one more point to implementing native decimals in PHP.
This is either a bug in the extension or a bug in PHP 8.3 (which I doubt)
but I will have a look at this.
Gina P. Banyard
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 floatIt 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.5This 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 conversionAll 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
Hi,
While I do think it would be beneficial to have a built-in decimal
type, regarding ext-decimal, I think some of the points raised may be outdated?
The documentation site seems to have moved to https://php-decimal.github.io https://php-decimal.github.io/, which also indicates that it implements operator overrides.
Cheers
Stephen
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 floatIt 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.5This 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 conversionAll 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 PravdinHi,
While I do think it would be beneficial to have a built-in
decimal
type, regarding ext-decimal, I think some of the points raised may be outdated?The documentation site seems to have moved to https://php-decimal.github.io https://php-decimal.github.io/, which also indicates that it implements operator overrides.
Cheers
Stephen
Hello Stephen,
I just ran apt install php8.3-decimal
and tried this:
$a = new Decimal\Decimal("1", 2);
$b = $a + $a;
PHP Warning: Uncaught TypeError: Unsupported operand types:
Decimal\Decimal + Decimal\Decimal in
So, it appears not.
On Wed, Dec 13, 2023 at 6:11 PM Robert Landers landers.robert@gmail.com
wrote:
I just ran apt install php8.3-decimal
and tried this:
$a = new Decimal\Decimal("1", 2);
$b = $a + $a;
PHP Warning: Uncaught TypeError: Unsupported operand types:
Decimal\Decimal + Decimal\Decimal inSo, it appears not.
I've pointed out this issue earlier in the discussion and Gina P. Banyard
has replied that this can be a PHP 8.3 bug and he will look into it.
BTW, my main concern is that this amazing extension doesn't look like it is
actively maintained and I'm worrying if I will be able to maintain my
projects that depend on it in the future.
Best,
Alex
On Wed, 13 Dec 2023 at 09:29, Alexander Pravdin alex.pravdin@interi.co
wrote:
On Wed, Dec 13, 2023 at 6:11 PM Robert Landers landers.robert@gmail.com
wrote:I just ran
apt install php8.3-decimal
and tried this:$a = new Decimal\Decimal("1", 2);
$b = $a + $a;
PHP Warning: Uncaught TypeError: Unsupported operand types:
Decimal\Decimal + Decimal\Decimal inSo, it appears not.
I've pointed out this issue earlier in the discussion and Gina P. Banyard
has replied that this can be a PHP 8.3 bug and he will look into it.BTW, my main concern is that this amazing extension doesn't look like it is
actively maintained and I'm worrying if I will be able to maintain my
projects that depend on it in the future.
Hot take, why not fund the maintainer (or someone else to fork and pick up
the work) to work on this extension?
It is not like most bundled extension within php-src are truly actively
maintained.
So why would integrating ext/decimal mean it will be maintained?
Just have a look at all the XML/DOM related extensions that were riddled
with bugs and unmaintained until Nils decided to pick up the work to
scratch an itch.
Moreover, being separate from "core" is frankly a benefit, and I very much
dislike the current status of having so many bundled extensions that cannot
improve at their own pace.
Having extensions not be tied to php-src's release cycle means they can
move more quickly, fix bugs faster, evolve more easily, etc.
Just look at ext/curl, being stuck at a certain level of support for
libcurl because it must be tied to the annual PHP release cycle.
Best regards,
Gina P. Banyard
PS: My pronouns are she/her
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.
Adding a new native type to PHP will create a large change. Not only is
it "just" adding a new native type, it also means all of the conversions
between types need to be added. This is not a small task.
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 floatIt uses the precision and scale defined in php.ini.
If you want to use arbitrary precision natives, then a precision and
scale as defined in php.ini defeats the purpose. Every installation can
then potentially calculate things in a different way.
The only way how to prevent that, is to have actual Decimal type, such
as the Decimal type in MongoDB uses (the IEEE 754 decimal128 type):
- https://www.mongodb.com/docs/mongodb-shell/reference/data-types/#std-label-shell-type-decimal
- https://en.wikipedia.org/wiki/Decimal128_floating-point_format
cheers,
Derick
--
https://derickrethans.nl | https://xdebug.org | https://dram.io
Author of Xdebug. Like it? Consider supporting me: https://xdebug.org/support
mastodon: @derickr@phpc.social @xdebug@phpc.social
Adding a new native type to PHP will create a large change. Not only is
it "just" adding a new native type, it also means all of the conversions
between types need to be added. This is not a small task.
I understand this :)
If you want to use arbitrary precision natives, then a precision and
scale as defined in php.ini defeats the purpose. Every installation can
then potentially calculate things in a different way.The only way how to prevent that, is to have actual Decimal type, such
as the Decimal type in MongoDB uses (the IEEE 754 decimal128 type):
If PHP core experts think that 128-bit decimal will cover the vast
majority of cases and is worth implementing, I'm totally for it. If we
can implement something standardized then fine. The current thread is
not an RFC candidate, but a kinda discussion board to formulate the
design principles and strategy.
On another note, is it possible to make zval variable size - 64 or 128
bits? So the 128-bit decimal can be a struct that will be held in the
stack instead of pointer manipulations. Can it be achieved with the
help of macroses?
--
Best, Alexander
Hello everyone. To continue the discussion, I'm suggesting an updated
version of my proposal. The main change is: to use decimal128 as the
backend for native decimals, so no 3rd-party libraries are required.
Even if adopting a library, it looks like it'll be much easier than
the previous proposal. Also, the principle "if any operand is decimal,
the result is decimal" is adopted, and conversion requirements are
relaxed and more specific.
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
Native decimals are backed by 128-bit decimals according to IEEE
754-2008. Point to discuss: can we use 128-bit zvals or transpile
decimals into a pair of 64-bit zvals representing lo and hi portions?
Decimal to float conversion is allowed and smooth:
function f (float $value) {}
f(0.2);
f(0.2d); // allowed with no warnings or errors
Function "str_to_decimal" added to convert from string representation
of numbers to decimals.
Typecast from string to decimal works the same as the "str_to_decimal" function.
The function "float_to_decimal" is added to explicitly convert floats
to decimals. Internally, it performs explicit float to string
conversions using existing defaults and also accepts an optional
parameter to define the number of fractional digits to round to. After
converting a float to a string, it converts the string to a 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 optimized algorithms are welcome.
Explicit type cast "(decimal) float" is the same as using
float_to_decimal with defaults.
Implicit conversion from float to decimal with type juggling works the
same as "float_to_decimal" with defaults but throws a warning to
encourage users to use explicit conversion instead: "Implicit float to
decimal conversion may cause unexpected results due to possible
rounding errors".
With strict types, implicit conversion is not possible and generates a
TypeError.
Literal numbers in the code are compiled to floats by default. If
prepended by the "(decimal)" typecast, or the "d" modifier is used
(0.2d) the decimal is produced by the compiler without an intermediary
float or string.
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. With
default_decimal, fractional string literals are compiled into
decimals.
Without default_decimal:
$var = 5 / 2; // returns float 2.5
$a = 3.02; // compiled to float
With default_decimal:
$var = 5 / 2; // returns decimal 2.5
$a = 3.02; // compiled to decimal
I understand that this point is controversial, so it can be postponed
and decided later.
The (decimal) typecast applied to a math operation produces a decimal
result without intermediary float by converting all operands to
decimals before calculating:
$var = 5 / 2; // returns float 2.5
$var = (decimal)(5 / 2); // returns decimal 2.5
$a = 5;
$b = 2;
$var = (decimal)($a / $b); // returns decimal 2.5
If any of the math operation operands are decimal, the result is
decimal. Floats are converted to decimals with implicit conversion
rules mentioned above:
Without strict types:
$f = (float) 0.2;
$d = (decimal) 0.2;
$r = $f + $d; // produces a warning about implicit conversion, returns decimal
$r = (decimal)$f + $d; // works with no warnings
With strict types:
$f = (float) 0.2;
$d = (decimal) 0.2;
$r = $f + $d; // produces TypeError
$r = (decimal)$f + $d; // works with no warnings
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 any
of the parameters is decimal, they return decimal. Float parameters
are converted to decimals implicitly according to the conversion rules
mentioned above.
I hope this version of the design is closer to being accepted by the
community. Please share your thoughts.
--
Best,
Alexander
--
Best regards,
Alex Pravdin
Interico
On Wed, Apr 10, 2024 at 12:55 AM Alexander Pravdin
alex.pravdin@interi.co wrote:
Adding a new native type to PHP will create a large change. Not only is
it "just" adding a new native type, it also means all of the conversions
between types need to be added. This is not a small task.I understand this :)
If you want to use arbitrary precision natives, then a precision and
scale as defined in php.ini defeats the purpose. Every installation can
then potentially calculate things in a different way.The only way how to prevent that, is to have actual Decimal type, such
as the Decimal type in MongoDB uses (the IEEE 754 decimal128 type):If PHP core experts think that 128-bit decimal will cover the vast
majority of cases and is worth implementing, I'm totally for it. If we
can implement something standardized then fine. The current thread is
not an RFC candidate, but a kinda discussion board to formulate the
design principles and strategy.On another note, is it possible to make zval variable size - 64 or 128
bits? So the 128-bit decimal can be a struct that will be held in the
stack instead of pointer manipulations. Can it be achieved with the
help of macroses?--
Best, Alexander
On Sat, Apr 27, 2024 at 11:04 PM Alexander Pravdin alex.pravdin@interi.co
wrote:
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 any
of the parameters is decimal, they return decimal. Float parameters
are converted to decimals implicitly according to the conversion rules
mentioned above.
So, as I mentioned months ago, this is the reason that having actually
looked into implementing things like this, I was interested in using a
library. Proposing this is fine. But doing a fully custom implementation
that includes this? You're going to implement sin
and cos
and atan
for 128-bit decimals? When we could use an open source library that has a
compatible license instead and is proven to work for these already instead,
likely with better performance as well?
This is likely to be more work than doing a type backed by a library while
also being less capable.
I know that your shift in proposal here is not aimed at me, and also I'm
not a voter so in that sense it doesn't matter. But if this is what the
proposal ends up being, I'll probably just continue on the research for an
actual arbitrary precision implementation based on MPFR instead of helping
with this implementation.
Jordan
Hello everyone. To continue the discussion, I'm suggesting an updated
version of my proposal.
This all sounds very useful ... but it also sounds like several months of full-time expert development.
Before you begin, I think it will be really important to define clearly what use cases you are trying to cater for, and who your audience is. Only then can you define a minimum set of requirements and goals.
It seems to me that the starting point would be an extension with a decimal type as an object, and implementations for all the operations you want to support. You'll probably want to define that more clearly than "anything in the language which takes a float".
What might seem like it would be the next step is converting the object to a "native type", by adding a new case to the zval struct. Not only would this require a large amount of work to start with, it would have an ongoing impact on everyone working with the internals.
I think a lot of the benefits could actually be delivered without it, and as separate projects:
-
Optimising the memory performance of the type, using copy-on-write semantics rather than eager cloning. See Gina's recent thread about "data classes".
-
Overloading existing functions which accept floats with decimal implementations. Could potentially be done in a similar way to operator overloads and special interfaces like Countable.
-
Convenient syntax for creating decimal values, such as 0.2d, declare(default_decimal), or having (decimal) casts affecting the tree of operations below them rather than just the result. This just needs the type to be available to the compiler, not a new zval type - for instance, anonymous function syntax creates a Closure object.
There may be other parts I've not mentioned, but hopefully this illustrates the idea that "a native decimal type" doesn't have to be one all-or-nothing project.
Regards,
Rowan Tommins
[IMSoP]
On Sun, Apr 28, 2024 at 11:36 AM Rowan Tommins [IMSoP]
imsop.php@rwec.co.uk wrote:
Hello everyone. To continue the discussion, I'm suggesting an updated
version of my proposal.This all sounds very useful ... but it also sounds like several months of full-time expert development.
Before you begin, I think it will be really important to define clearly what use cases you are trying to cater for, and who your audience is. Only then can you define a minimum set of requirements and goals.
It seems to me that the starting point would be an extension with a decimal type as an object, and implementations for all the operations you want to support. You'll probably want to define that more clearly than "anything in the language which takes a float".
What might seem like it would be the next step is converting the object to a "native type", by adding a new case to the zval struct. Not only would this require a large amount of work to start with, it would have an ongoing impact on everyone working with the internals.
I think a lot of the benefits could actually be delivered without it, and as separate projects:
Optimising the memory performance of the type, using copy-on-write semantics rather than eager cloning. See Gina's recent thread about "data classes".
Overloading existing functions which accept floats with decimal implementations. Could potentially be done in a similar way to operator overloads and special interfaces like Countable.
Convenient syntax for creating decimal values, such as 0.2d, declare(default_decimal), or having (decimal) casts affecting the tree of operations below them rather than just the result. This just needs the type to be available to the compiler, not a new zval type - for instance, anonymous function syntax creates a Closure object.
There may be other parts I've not mentioned, but hopefully this illustrates the idea that "a native decimal type" doesn't have to be one all-or-nothing project.
Regards,
Rowan Tommins
[IMSoP]
I'm not so sure this could be implemented as an extension, there just
isn't the right hooks for it.
Robert Landers
Software Engineer
Utrecht NL
I'm not so sure this could be implemented as an extension, there just
isn't the right hooks for it.
The whole point of my email was that "this" is not one single feature, but a whole series of them. Some of them can be implemented as an extension right now; some could be implemented as an extension by adding more hooks which would also be useful for other extensions; some would need changes to the core of the language.
If the aim is "everything you could possibly want in a decimal type", it certainly can't be an extension; if the aim is "better support for decimals", then it possibly can.
Regards,
Rowan Tommins
[IMSoP]
On Tue, 30 Apr 2024 at 09:19, Rowan Tommins [IMSoP] imsop.php@rwec.co.uk
wrote:
On 28 April 2024 07:47:40 GMT-07:00, Robert Landers <
landers.robert@gmail.com> wrote:I'm not so sure this could be implemented as an extension, there just
isn't the right hooks for it.The whole point of my email was that "this" is not one single feature, but
a whole series of them. Some of them can be implemented as an extension
right now; some could be implemented as an extension by adding more hooks
which would also be useful for other extensions; some would need changes to
the core of the language.If the aim is "everything you could possibly want in a decimal type", it
certainly can't be an extension; if the aim is "better support for
decimals", then it possibly can.Regards,
Rowan Tommins
[IMSoP]
I think setting some expectations in the proper context is warranted here.
- Would a native decimal type be good for the language? I would say we
probably are not going to find many if any people who would be against it. - Is there a need for it? Well, the whole world of e-commerce, accounting
and all kinds of business systems that deal with money in PHP world do not
leave any room for doubt - https://packagist.org/?query=money . The use
case is right there :) - In most cases people need decimal precision math in the bounds of what
the decimal128 standard provides. Most of us just do not want the float
drift and while a signed 64-bit integer is big, it has it's limitations and
a need for edge layer transformations be it presentation, API endpoints or
storing it in the database or other storage mediums. - Is it a lot of engine work? Yes, yes it is. Is it worth it? I think yes,
especially if we get buying from most of the active maintainers and get a
project going for it. This is not going to be the first or last big engine
project. But this might warrant a PHP 9 release in the end :) - But BCMath/GMP/etc!!! Well, extensions are optional. They are also not
as fast and they deal with strings. They are not as fast and you will have
to rely on that extension's methods that most math functions are
implemented and all that stuff. Frankly, I never had a use case where
BCMath was not an overkill. And doing number crunching with BCMath is just
slow due to them being strings internally. The use cases are just
different. They solve a different problem than a decimal128 does for the
language.
I think all the discussions on the subject have shown that BCMath RFC is
it's own thing and adding a decimal type to the PHP language/engine is it's
own thing. They are not mutually exclusive and solve different problems.
--
Arvīds Godjuks
+371 26 851 664
arvids.godjuks@gmail.com
Telegram: @psihius https://t.me/psihius
I think setting some expectations in the proper context is warranted here.
- Would a native decimal type be good for the language? I would say we
probably are not going to find many if any people who would be against it.
As I said earlier, I don't think that's the right question, because "adding a native type" isn't a defined process. Better questions are: Should a decimal type be always available? Does a decimal type need special features to maximise performance? Should we have special syntax for a decimal type? What functions should support a decimal type, or have versions which do?
- Is there a need for it? Well, the whole world of e-commerce, accounting
and all kinds of business systems that deal with money in PHP world do not
leave any room for doubt - https://packagist.org/?query=money . The use
case is right there :)
That's a great example - would a decimal type make those libraries redundant? Probably not - they provide currency and rounding facilities beyond basic maths. Would those libraries benefit from an always-available, high-performance native type? Certainly.
Would they benefit from it having strong integration into the syntax and standard library of the language? Not really; there's a small amount of actual code dealing with the values.
- Is it a lot of engine work?
Only if we go for the maximum ambition, highly integrated into the language.
Is it worth it?
I'm actually not convinced.
- But BCMath/GMP/etc!!! Well, extensions are optional.
Extensions are only optional if we decide they are. ext/json used to be optional, but now it's always-on.
They are also not as fast and they deal with strings.
Not as fast as what? If someone wants to make an extension around a faster library, they can. And only BCMath acts directly on strings; other libraries use text input to create a value in memory - whether that's a PHP string or a literal provided by the compiler doesn't make much difference.
I absolutely think there are use cases for decimal types and functions; but "I want a faster implementation" and "I want to add a new fundamental type to the language, affecting every corner of the engine" are very different things.
Regards,
Rowan Tommins
[IMSoP]