Conversation
Kotlin renders a Double in scientific notation once its magnitude reaches 1e7,
or falls below 1e-3, so 29638000.0 prints as 2.9638E7. DECIMAL_REGEX has no
exponent branch, so validateAndNormalizeDecimalLiteral rejected the literal the
platform had just produced and Deci(Double) threw DeciParseException for every
value from ten million up. 9999999.0 was accepted and 10000000.0 was not: the
boundary sat exactly where Double.toString() changes format.
Double.toDecimalLiteral() expands the exponent in place before the value reaches
the String constructor, and all six actuals now use it. NaN and the infinities
carry no exponent marker, pass through unchanged and are still rejected.
The String constructor's accepted grammar is untouched — Deci("1e5") is rejected
as before. Teaching DECIMAL_REGEX an exponent branch would have fixed this too,
but it widens the input surface for every caller, which is a separate decision.
Closes Kimplify#6
Member
|
Thanks for the fix. The iOS test exposed an edge case: |
Merkost
force-pushed
the
fix/double-constructor-scientific-notation
branch
from
September 24, 2026 04:18
6831442 to
b177056
Compare
Author
|
Thanks for catching that and for the fix. Agreed: throwing Two small things:
Separately, literals with more than 38 significant digits are still rounded silently on Apple. Out of scope here, but happy to open an issue if you'd like it tracked. |
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.
Closes #6
Summary
Deci(Double)threwDeciParseExceptionfor every magnitude at or above1e7, and below1e-3.All six
actualconstructors arethis(value.toString()), and Kotlin switchesDouble.toString()to scientific notation at exactly those thresholds —29638000.0prints as2.9638E7.DECIMAL_REGEXhas no exponent branch, sovalidateAndNormalizeDecimalLiteralrejected the literal the platform had just produced.9999999.0was accepted and10000000.0was not; the boundary was the formatting change, not the value.Double.toDeci()inherited it.Double.toDecimalLiteral()(internal,commonMain, pure Kotlin) expands the exponent in place before the value reaches theStringconstructor, and all six actuals now use it.NaNand the infinities carry no exponent marker, pass through unchanged, and are still rejected.No change to the
Stringconstructor's accepted grammar —Deci("1e5")is rejected as before. TeachingDECIMAL_REGEXan exponent branch would fix this too, but it widens the input surface for every caller, so I left that as a separate decision for you.No public API change, so the ABI dump is untouched.
Before / after
Changelog
Deci(Double)no longer throws on ordinary magnitudes. Kotlin renders aDoublein scientific notation once its magnitude reaches1e7, or falls below1e-3—29638000.0prints as2.9638E7. That literal is outside the grammarDECIMAL_REGEXaccepts, so theDoubleconstructor rejected it withDeciParseException: every value from ten million up, which for a currency amount is not an edge case. The constructor now expands the exponent before validating, on all six targets.NaNand the infinities are still rejected, and theStringconstructor's accepted grammar is unchanged — scientific notation is expanded only on theDoublepath.NaN.NSDecimalNumberholds a narrower range than aDouble(or a long decimal literal), so on Apple targetsDeci(Double.MAX_VALUE)used to produceNaNand very small magnitudes collapsed to zero. Both theDoubleandStringconstructors now throwDeciOverflowExceptionfor such input on Apple. JVM, Android, JS and WasmJs are unaffected.Test Plan
Two new test classes in
commonTest:parser/DoubleLiteralTest— table-driven overexpandScientificNotation, covering both exponent signs,e/E, an explicit+exponent (1e+21, the JS form), a non-integer exponent, and the non-finite literals. Assertions ontoDecimalLiteralcheck only that no exponent marker survives, sinceDouble.toString()itself differs per target and an exact-string expectation would not be portable.DeciDoubleConstructorTest— the constructor's own behaviour, asserted against string-constructedDecivalues per the repo's testing conventions: sign handling, arithmetic on expanded values, and that non-finite input still fails.iosTest/DeciDoubleConstructorIosTest(added by @Merkost) — values outsideNSDecimalNumber's range (Double.MAX_VALUE,Double.MIN_VALUE, a 309-digit literal) throwDeciOverflowExceptionon Apple, and ordinary scientific-notation input is accepted.JVM tests pass (
./gradlew :deci:jvmTest)JS tests pass (
./gradlew :deci:jsTest)WasmJs tests pass (
./gradlew :deci:wasmJsTest)Lint passes (
./gradlew :deci:ktlintCheck)API compatibility verified (
./gradlew :deci:checkKotlinAbi)iOS —
:deci:compileKotlinIosSimulatorArm64and:deci:compileTestKotlinIosSimulatorArm64pass, butlinkDebugTestIosSimulatorArm64fails on my machine withFailed to build cache for kotlinx-serialization-core-iosSimulatorArm64Main-1.11.0.klib. That reproduces on unmodifiedmain, so it is a local Kotlin/Native cache problem rather than anything in this change — leaving it to CI's clean runner.How this was found
A POS app mapping
price: Doubleoff a JSON response intoDeci: the order list dropped into its server-error state on a Rp 29.638.000 transaction. The quieter half is worth flagging — where such a value reaches a formatter that catches broadly, the exception is swallowed and the raw2.9638E7is rendered where a price belongs.