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.prav...@interi.co> wrote:
>
> On Tue, Apr 9, 2024 at 7:52 PM Derick Rethans <der...@php.net> 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):
> >
> > - 
> > https://www.mongodb.com/docs/mongodb-shell/reference/data-types/#std-label-shell-type-decimal
> > - https://en.wikipedia.org/wiki/Decimal128_floating-point_format
>
> 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

Reply via email to