Hi Barney, Jordan,

> I think that's a sufficiently different data type that it should be a 
> different class (if required), and probably a separate RFC, and for now it's 
> better to stay closer to the existing BCMath API.
> 
> Developers should be prepared to accept that an arbitrary precision decimal 
> can't represent 1/3 exactly, just like a binary float can't represent 1/10 
> exactly.

> Again, my experience on the issue is with the development of my own library 
> on the issue, however in my case I fully separated that kind of object into 
> its own class `Fraction`, and gave the kinds of operations we've been 
> discussing to the class `Decimal`. Storing numerators and denominators for as 
> long as possible involves a completely different set of math. For instance, 
> you need an algorithm to determine the Greatest Common Factor and the Least 
> Common Multiple in such a class, because there are a lot of places where you 
> would need to find the smallest common denominator or simplify the fraction.
> 
> Abstracting between the `Fraction` and `Decimal` so that they worked with 
> each other honestly introduced the most complex and inscrutable code in my 
> entire library, so unless fractions are themselves also a design goal of this 
> RFC, I would recommend against it.

> Having two classes `Fraction` and `Decimal` necessitated that I had a 
> `Number` class they both extended, as there are many situations where I would 
> want to type-hint "anything that calculation can be done on with arbitrary 
> precision" instead of specifically one or the other. I also provided the 
> `NumberInterface`, `DecimalInterface`, and `FractionInterface`, though I 
> don't think that would be necessary here as this is much more just a wrapper 
> for BCMath than an extension of it. The main goal of my library was not to 
> act as a wrapper for BCMath, it was to EXTEND BCMath with additional 
> capabilities, such as trigonometric functions that have arbitrary precision, 
> so keep that in mind when weighing input of mine that is referencing the work 
> I have done on this topic. The design goals were different.

Agree. I was a little too focused on precision and lost sight of the larger 
goal.


> In my library, if the scale is unspecified, I actually set the scale to 10 OR 
> the length of the input string, including integer decimals, whichever is 
> larger. Since I was designing my own library I could do things like that as 
> convention, and a scale of 10 is extremely fast, even with the horrifically 
> slow BCMath library, but covers most use cases (the overwhelmingly common of 
> which is exact calculation of money).
> 
> My library handles scale using the following design. It's not necessarily 
> correct here, as I was designing a PHP library instead of something for core, 
> AND my library does not have to deal with operator overloads so I'm always 
> working with method signatures instead, AND it's possible that my 
> class/method design is inferior to other alternatives, however it went:
> 
> 1. Each number constructor allowed for an optional input scale.
> 2. The input number was converted into the proper formatting from allowed 
> input types, and then the implicit scale is set to the total number of digits.
> 3. If the input scale was provided, the determined scale is set to that value.
> 4. Otherwise, the determined scale at construction is set to 10 or the 
> implicit scale of "number of digits", whichever is larger.
> 5. The class contained the `roundToScale` method, which allowed you to 
> provide the desired scale and the rounding method, and then would set the 
> determined scale to that value after rounding. It contained the `round` 
> method with the same parameters to allow rounding to a specific scale without 
> also setting the internal determined scale at the same time.
> 6. The class contained the `setScale` method which set the value of the 
> internal determined scale value to an int without mutating the value at all.
> 7. All mathematical operation methods which depended on scale, (such as div 
> or pow), allowed an optional input scale that would be used for calculation 
> if present. If it was not present, the internal calculations were done by 
> taking the higher of the determined scale between the two operands, and then 
> adding 2, and then the result was done by rounding using the default method 
> of ROUND_HALF_EVEN if no rounding method was provided.
> 
> Again, though I have spent a lot of design time on this issue for the math 
> library I developed, my library did not have to deal with the RFC process for 
> PHP or maintain consistency with the conventions of PHP core, only with the 
> conventions it set for itself. However, I can provide a link to the library 
> for reference on the issue if that would be helpful for people that are 
> contributing to the design aspects of this RFC.

The two use cases at issue here are when the div and pow's exponent are 
negative values. So how about allowing only these two methods to optionally set 
`$scale` and `$roundMode` ?

- The constructor takes only `$num` and always uses implicit scaling. There is 
no option for the user to specify an arbitrary scale.
- `$scale`: If specified, use that value, otherwise use `10`. The scale 
specified here is added to the scale of the left operand and used as the scale 
of the result. In other words, `(new Number('0.01')->div('3', 2))` results in 
`'0.0030' // scale = 2 + 2 = 4`.
- `$roundMode`: Specifies the rounding method when the result does not fit 
within the scale. The initial value is `PHP_ROUND_TOWARD_ZERO`, which matches 
the behavior of the BCMath function. That is, just truncate.
- If lucky enough to get the result within the scale, apply the implicit scale 
to the result. In other words, if calculate `1 / 2`, the resulting scale will 
be `1`, even if scale is `null` or specify a value such as `20` for scale.
- The result of a calculation with operator overloading is the same as if the 
option was not used when executing the method.

However, I'm not sure if naming it `$scale` is appropriate.

Also, since `BCMath\Number` is not made into a final class, there is a 
possibility of implementing an inherited class in userland. Regarding this, is 
it better to make the calculation method a final method, or to use a function 
overridden by the user when executing from the opcode?

Regards.

Saki

Reply via email to