Re: [RFC] [Discussion] Support object type in BCMath

From: Date: Tue, 02 Apr 2024 23:50:43 +0000
Subject: Re: [RFC] [Discussion] Support object type in BCMath
References: 1 2 3 4 5 6 7 8 9 10  Groups: php.internals 
Request: Send a blank email to internals+get-122902@lists.php.net to get a copy of this message
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

« previous php.internals (#122902) next »