Skip to content

Add rounding modes, withScale(), allocation and helper methods - #24

Merged
dereuromark merged 1 commit into
masterfrom
feature/api-additions
Oct 11, 2026
Merged

dereuromark merged 1 commit into
masterfrom
feature/api-additions

Conversation

@dereuromark

@dereuromark dereuromark commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

New API for the next minor, with the documentation pages for it.

Nothing existing changes. Previous and new class were run over the same 10,000 calls and differ in one place: round() with a negative scale threw and now rounds to tens and hundreds.

Rounding

  • ROUND_HALF_DOWN, ROUND_HALF_EVEN (banker's rounding), ROUND_HALF_ODD, ROUND_UP, ROUND_DOWN
  • withScale(int $scale, int $roundMode) pads or rounds to an exact scale. It applies every mode at that scale, including floor and ceil, which round() cannot do in 1.x.
  • round() with a negative scale: Decimal::create('1250')->round(-2) is 1300

The new constants have their own values (9 to 13) and not those of PHP_ROUND_HALF_DOWN, PHP_ROUND_HALF_EVEN and PHP_ROUND_HALF_ODD. In 1.x round() treats an unknown mode as half up, so reusing 2, 3 and 4 would silently change tie results for anyone who passes the PHP constants today. 2.0 can align them, or switch to the native RoundingMode enum.

Input

  • Exponent limit: scientific notation beyond Decimal::MAX_EXPONENT (10000) throws. '1e1000000000' is twelve characters and used to allocate a billion digits.
  • Decimal::tryCreate() returns null for null and for input that is not numeric.

Calculation

  • shift(int $places) moves the decimal point
  • Decimal::sum(), average(), min(), max() over any iterable
  • allocate(array $ratios) and split(int $parts) divide a value into shares that sum to it exactly: 100.00 split by 3 is 33.34, 33.33, 33.33
  • between(), clamp(), isEven(), isOdd()

Left out

  • A default scale for divide() (API 2.0 thoughts #10). Every candidate default gives 1 / 3 = 0 for integer operands, so that needs a decision first.
  • toFloat() accepting long fractions. The tests pin the current exception on purpose, so that belongs with the 2.0 changes.

@dereuromark dereuromark added the enhancement New feature or request label Oct 11, 2026
@codecov

codecov Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.20%. Comparing base (a43e913) to head (81e9150).

Additional details and impacted files
@@             Coverage Diff              @@
##             master      #24      +/-   ##
============================================
+ Coverage     95.65%   97.20%   +1.55%     
- Complexity      114      176      +62     
============================================
  Files             1        1              
  Lines           230      358     +128     
============================================
+ Hits            220      348     +128     
  Misses           10       10              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Base automatically changed from docs-site to master October 11, 2026 14:52
All additions. A run of the previous and the new class over the same
10,000 calls differs in one place only: round() with a negative scale
returned an exception and now rounds to tens, hundreds and so on.

Rounding
- ROUND_HALF_DOWN, ROUND_HALF_EVEN, ROUND_HALF_ODD, ROUND_UP and
  ROUND_DOWN. They get their own values (9 to 13) and not those of the
  PHP_ROUND_* constants: round() treats unknown modes as half up in
  1.x, so reusing 2, 3 and 4 would silently change the result for
  callers that pass PHP_ROUND_HALF_EVEN today.
- withScale() pads or rounds to an exact scale and applies every mode
  at that scale, including floor and ceil, which round() cannot do in
  1.x.
- round() accepts a negative scale for the non-directed modes.

Input
- Scientific notation is limited to exponents up to MAX_EXPONENT
  (10000). "1e1000000000" used to allocate a billion digits.
- tryCreate() returns null for null and non numeric input.

Calculation
- shift() moves the decimal point.
- sum(), average(), min() and max() over any iterable.
- allocate() and split() divide a value into shares that sum to it
  exactly, at the scale of the value.
- between(), clamp(), isEven() and isOdd().

All bcmath calls in the new code pass their scale. A global bcscale()
set by the application would otherwise leak into the results.
@dereuromark
dereuromark force-pushed the feature/api-additions branch from 7e3476e to 81e9150 Compare October 11, 2026 14:53
@dereuromark
dereuromark enabled auto-merge (squash) October 11, 2026 14:53
@dereuromark
dereuromark disabled auto-merge October 11, 2026 14:54
@dereuromark
dereuromark merged commit a1ec406 into master Oct 11, 2026
7 checks passed
@dereuromark
dereuromark deleted the feature/api-additions branch October 11, 2026 14:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant