Skip to content

Deci(Double) throws DeciParseException for any magnitude >= 1e7 #6

Description

@DXMHXR

Summary

Deci(Double) rejects every value whose magnitude is at or above 1e7, or below 1e-3.

All six actual constructors are this(value.toString()), and Kotlin switches Double.toString() to scientific notation at those thresholds. DECIMAL_REGEX has no exponent branch, so validateAndNormalizeDecimalLiteral rejects the literal the platform just produced.

Ten million of any currency is an ordinary amount, so in a financial codebase — which is what the library is for — this is reachable from normal data rather than from an edge case.

Reproduction

Against main (b8648e1), :deci:jvmTest:

for (v in listOf(9_999_999.0, 10_000_000.0, 29_638_000.0, 1.0e-3, 1.0e-4, -1.5e8)) {
    println("$v -> toString='$v' -> " + runCatching { Deci(v).toString() })
}
9999999.0   -> toString='9999999.0' -> Success(9999999.0)
1.0E7       -> toString='1.0E7'     -> Failure(DeciParseException: Invalid decimal literal: '1.0E7')
2.9638E7    -> toString='2.9638E7'  -> Failure(DeciParseException: Invalid decimal literal: '2.9638E7')
0.001       -> toString='0.001'     -> Success(0.001)
1.0E-4      -> toString='1.0E-4'    -> Failure(DeciParseException: Invalid decimal literal: '1.0E-4')
-1.5E8      -> toString='-1.5E8'    -> Failure(DeciParseException: Invalid decimal literal: '-1.5E8')

9999999.0 succeeds and 10000000.0 throws — the boundary is exactly where Double.toString() changes format, not anything about the value.

Double.toDeci() in DeciExtensions is Deci(this), so it carries the same behaviour.

Expected

Deci(Double) is total for every finite Double. The KDoc documents @throws DeciParseException on the String constructor only; on the Double constructor it warns about float artifacts, which reads as "the value may be imprecise", not "the call may fail".

Impact

Found in a POS application: an order list mapped price: Double from a JSON response into Deci and the whole screen fell into its server-error state on a Rp 29.638.000 transaction. Worth noting the failure is not always loud — where the value reaches a formatter that catches broadly, the exception is swallowed and the raw 2.9638E7 is rendered where a price belongs.

Suggested fix

Expand the exponent on the Double path before validating, leaving the String constructor's accepted grammar untouched. That keeps Deci("1e5") rejected as it is today, which feels like a separate API decision.

The alternative — teaching DECIMAL_REGEX an exponent branch — would fix this too, but it widens the accepted input surface for every caller, so I did not assume it.

PR follows.

Environment

  • Deci 0.3.0 and main (b8648e1)
  • Kotlin Multiplatform, reproduced on the JVM target; the constructor is identical on all six

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions