Repository navigation
Never drop a digit without being told how - #27
Merged
Merged
Conversation
2.0 gets one rule: a result is exact, or the caller names the rounding. Arithmetic - add(), subtract(), multiply() and mod() return the exact result. A scale argument that would lose a digit other than zero needs a rounding mode as third argument, otherwise RoundingNecessaryException is thrown. 1.x cut off. - divide(), sqrt() and average() default to the new ROUND_UNNECESSARY, so an inexact result has to be rounded on purpose. ROUND_DOWN gives the 1.x result. - pow() returns the exact power for a positive exponent and divides for a negative one. sqrt() rounds in any mode, decided on the exact comparison of the candidate with the value. Values - A scale passed to the constructor pads the string and refuses to lose digits. The string therefore always shows the scale, and the strict flag is gone because its check is always on. - Floats are rejected by the constructor and by every method that takes a value. The types still list float: without it PHP converts a float to an integer for callers not using strict types, which would truncate 1.5 to 1 without any error. createFromFloat() is the one way in. - toFloat() returns the nearest float instead of refusing long fractions. isBigDecimal() is removed. Exceptions - Own classes under Exception\, all implementing DecimalException and each extending the SPL type thrown before where there was one. bcmath's ValueError no longer surfaces from sqrt() of a negative value or from a fractional pow() exponent. Rounding modes - Every mode argument also accepts PHP 8.4's native RoundingMode. The enum is matched by case name, so the class loads on 8.2 and 8.3. A copy of the enum under tests/stubs is scanned by PHPStan only, to analyse the type on those versions. Serialization - __serialize() stores the decimal string. __unserialize() still reads the property based format of 1.x, including a declared scale larger than the stored decimals.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## next #27 +/- ##
============================================
- Coverage 98.00% 95.59% -2.42%
- Complexity 190 201 +11
============================================
Files 1 1
Lines 401 454 +53
============================================
+ Hits 393 434 +41
- Misses 8 20 +12 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The remaining 2.0 behavior, built on one rule: the library never drops a digit unless the caller says how. A result is exact, or a rounding mode is named, or the call throws.
No silent digit loss
Decimal::create('10')->divide('3', 4)3.3333RoundingNecessaryExceptionDecimal::create('10')->divide('3', 4, Decimal::ROUND_HALF_UP)3.33333.3333Decimal::create('10')->divide('4', 2)2.502.50Decimal::create('18')->sqrt()4RoundingNecessaryExceptionDecimal::create('1.5')->pow(2)2.22.25Decimal::create('19.99')->multiply('0.19', 2)3.79RoundingNecessaryExceptionDecimal::create('0.129', 2)0.12RoundingNecessaryExceptionDecimal::create('0.120', 2)0.120.12ROUND_UNNECESSARYmode, the default ofdivide(),sqrt(),average()and of the scale argument onadd(),subtract(),multiply(),mod()andpow(). Dropping zeros is never an error.ROUND_DOWNreproduces the 1.x cut-off wherever it is wanted.sqrt()andpow()round in every mode.$strictargument is removed, its check is always on.Scale and string agree
Decimal::create('0.1', 4)prints0.1000. The scale of an object is always the number of decimals in its string.Floats
The constructor,
create()and every method taking a value reject floats with an exception that points tocreateFromFloat().The parameter types still list
float. Without it, PHP converts a float to an integer for callers that do not declare strict types, soDecimal::create(1.5)would become1without any error. Keeping the type and throwing is the only way to reject it for all callers.Exceptions
Six classes under
PhpCollective\DecimalObject\Exception, all implementingDecimalException:InvalidNumberException\InvalidArgumentExceptionInvalidArgumentException\InvalidArgumentExceptionRoundingNecessaryException\RuntimeExceptionDivisionByZeroException\DivisionByZeroErrorNegativeNumberException\DomainExceptionOverflowException\OverflowExceptionbcmath's
ValueErrorno longer surfaces fromsqrt()of a negative value or a fractionalpow()exponent.toInt()out of range throwsOverflowExceptionwhere it threwTypeError.Native
RoundingModeEvery mode argument is typed
RoundingMode|intand accepts PHP 8.4's enum next to the constants:The enum is matched by case name, so the class loads and works on PHP 8.2 and 8.3, where only the constants exist. Code written with the constants needs no change later. A copy of the enum in
tests/stubsis scanned by PHPStan only, so the type can be analysed on those versions.Other
toFloat()returns the nearest float for long fractions and throws only beyond the float range.isBigDecimal()is removed.__serialize()/__unserialize()store the decimal string. Data serialized by 1.x is still read, including a declared scale.pow()exponents are limited toMAX_EXPONENTin either direction.The class is not
finaland the properties stayprotected.Documentation is updated throughout, and
docs/reference/upgrading.mdlists every change with the 1.x and 2.0 result and what to do about it.One note for
dereuromark/cakephp-decimal: it passes database and request values straight toDecimal::create(). Any place that can hand it a float needs to go throughcreateFromFloat()or a string before the plugin allows^2.0.