Skip to content

feat(core): decimal-exact formatWithOptions + configurable RoundingMode#15

Merged
Merkost merged 6 commits into
mainfrom
feature/issue-14-decimal-exact
Jun 5, 2026
Merged

feat(core): decimal-exact formatWithOptions + configurable RoundingMode#15
Merkost merged 6 commits into
mainfrom
feature/issue-14-decimal-exact

Conversation

@Merkost

@Merkost Merkost commented Jun 5, 2026

Copy link
Copy Markdown
Member

Summary

Fixes #14. Makes CurrencyFormatter.formatWithOptions format from the decimal string with no Double round-trip, and adds a configurable rounding mode to CurrencyFormatOptions.

val opts = CurrencyFormatOptions { roundingMode = RoundingMode.HALF_UP }
CurrencyFormatter(KurrencyLocale.US).formatWithOptions("2.665", "USD", opts) // "$2.67"

Why

The options path was Double-based (toDoubleOrNull + round(value*factor)/factor), which lost precision beyond Double's ~15–17 significant digits — defeating the kurrency-deci overloads — and inherited rounding artifacts ("2.675" rendered 2.67 because 2.675*100 is 267.4999…).

What changed

  • internal object Decimals — pure string decimal arithmetic (no dependency): roundToScale(s, scale, mode) with carry + isZero/isNegative/abs/isOne.
  • RoundingMode (@Serializable): HALF_EVEN (default), HALF_UP, DOWN (truncate), UP.
  • CurrencyFormatOptions.roundingMode — default HALF_EVEN, backward-compatible.
  • CurrencyFormatterformatDecimal string-based; no toDouble in the value path; dead tenPow removed.
  • kurrency-deci overloads now genuinely exact; precision caveat removed.
  • Fixed a latent normalization bug the rewrite surfaced (raw leading + / locale-grouped input).

Behavior change (intended)

Default stays half-even but is now mathematically exact, correcting Double artifacts (2.675 → $2.68). No existing tests needed changing.

Testing

  • DecimalsTest (18), DecimalExactFormattingTest (6), serializer round-trip + default (8), deci exactness (1) — all commonTest, green on JVM/JS/WasmJs/iOS. Covers every mode, ties, carry, padding, scale 0, predicates, and beyond-Double magnitudes.
  • No dependency added to kurrency-core.

Merkost and others added 4 commits June 5, 2026 14:14
Pure foundation for decimal-exact formatting: a public RoundingMode enum
(HALF_EVEN, HALF_UP, DOWN, UP) and an internal Decimals object providing
roundToScale, isZero, isNegative, abs, and isOne — all operating on
decimal strings with no Double round-trip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…noring roundingMode

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…al-exact

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 5, 2026 04:40

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the formatWithOptions formatting engine in kurrency-core to be decimal-string based (eliminating the Double round-trip for the amount being formatted) and introduces a configurable RoundingMode on CurrencyFormatOptions, with corresponding cross-platform tests and kurrency-deci exactness coverage.

Changes:

  • Added RoundingMode (serializable) and CurrencyFormatOptions.roundingMode (default HALF_EVEN).
  • Implemented internal object Decimals for string-based rounding and predicates, and routed CurrencyFormatter.formatWithOptions/formatDecimal through it.
  • Added/updated tests to cover exact formatting beyond Double precision and rounding-mode behavior (core + deci).

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
kurrency-deci/src/commonTest/kotlin/org/kimplify/kurrency/deci/DeciExactFormattingTest.kt Adds a regression test proving Deci formatting remains exact beyond Double integer precision.
kurrency-deci/src/commonMain/kotlin/org/kimplify/kurrency/deci/DeciExtensions.kt Removes outdated docs warning about Double-based rounding/precision.
kurrency-core/src/commonTest/kotlin/org/kimplify/kurrency/serialization/CurrencyFormatOptionsSerializerTest.kt Adds serializer round-trip + defaulting tests for roundingMode.
kurrency-core/src/commonTest/kotlin/org/kimplify/kurrency/DecimalsTest.kt Adds unit tests for string-based rounding and helpers.
kurrency-core/src/commonTest/kotlin/org/kimplify/kurrency/DecimalExactFormattingTest.kt Adds integration tests validating exact formatting and rounding modes via formatWithOptions.
kurrency-core/src/commonMain/kotlin/org/kimplify/kurrency/RoundingMode.kt Introduces the new rounding-mode enum used by options and formatting.
kurrency-core/src/commonMain/kotlin/org/kimplify/kurrency/Decimals.kt Adds internal string-decimal rounding utilities used by the formatter.
kurrency-core/src/commonMain/kotlin/org/kimplify/kurrency/CurrencyFormatter.kt Reworks formatWithOptions to be decimal-string based and plumbs roundingMode into rounding.
kurrency-core/src/commonMain/kotlin/org/kimplify/kurrency/CurrencyFormatOptions.kt Adds roundingMode to options + builder, with docs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread kurrency-core/src/commonMain/kotlin/org/kimplify/kurrency/CurrencyFormatter.kt Outdated
Merkost and others added 2 commits June 5, 2026 14:55
isValidAmount accepts scientific notation (e.g. "1e3") via toDoubleOrNull, but
the string-based formatWithOptions path would emit garbage like "1e3.00". Expand
exponent notation to a plain decimal string first, matching the platform path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…onent cases

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Merkost Merkost assigned Merkost and unassigned Merkost Jun 5, 2026
@Merkost Merkost added the enhancement New feature or request label Jun 5, 2026
@Merkost Merkost merged commit 1df04fe into main Jun 5, 2026
2 checks passed
@Merkost Merkost deleted the feature/issue-14-decimal-exact branch June 5, 2026 06:38
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.

Make formatWithOptions decimal-exact (drop the Double round-trip)

2 participants