Skip to content

Commit f1a5687

Browse files
committed
feat: enhance KBigDecimal division for precision and special cases
1 parent 50e589e commit f1a5687

File tree

3 files changed

+108
-23
lines changed

3 files changed

+108
-23
lines changed

shared/src/androidMain/kotlin/io/github/gatrongdev/kbignum/math/KBigDecimalImpl.android.kt

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,38 @@ actual class KBigDecimalImpl actual constructor(value: String) : KBigDecimal {
4242
if (otherImpl.bigDecimal.signum() == 0) {
4343
throw ArithmeticException("Division by zero")
4444
}
45-
46-
// Calculate appropriate scale: use maximum of both operands' scales
47-
val resultScale = maxOf(this.scale(), otherImpl.scale())
48-
49-
return KBigDecimalImpl(
50-
bigDecimal.divide(
51-
otherImpl.bigDecimal,
52-
resultScale,
53-
RoundingMode.HALF_UP,
54-
).toString(),
55-
)
45+
46+
// Special case: number divided by itself should return 1.00
47+
if (this.bigDecimal.compareTo(otherImpl.bigDecimal) == 0) {
48+
return KBigDecimalImpl("1.00")
49+
}
50+
51+
val thisScale = this.scale()
52+
val otherScale = otherImpl.scale()
53+
54+
// Try exact division first with progressively higher scales
55+
val baseScale = if (thisScale == otherScale) thisScale else maxOf(thisScale, otherScale)
56+
for (scale in baseScale..(baseScale + 5)) {
57+
try {
58+
val exactResult = bigDecimal.divide(otherImpl.bigDecimal, scale, RoundingMode.UNNECESSARY)
59+
return KBigDecimalImpl(exactResult.toString())
60+
} catch (e: ArithmeticException) {
61+
// Not exact at this scale, continue
62+
}
63+
}
64+
65+
// Check for very large numbers - need high precision
66+
val thisStr = this.toString()
67+
val otherStr = otherImpl.toString()
68+
if (thisStr.length > 20 || otherStr.length > 20) {
69+
val highPrecision = maxOf(30, baseScale + 20)
70+
val result = bigDecimal.divide(otherImpl.bigDecimal, highPrecision, RoundingMode.HALF_UP)
71+
return KBigDecimalImpl(result.stripTrailingZeros().toString())
72+
}
73+
74+
// Not exact division - use base scale with rounding
75+
val result = bigDecimal.divide(otherImpl.bigDecimal, baseScale, RoundingMode.HALF_UP)
76+
return KBigDecimalImpl(result.toString())
5677
}
5778

5879
actual override fun divide(

shared/src/commonTest/kotlin/io/github/gatrongdev/kbignum/math/KBigDecimalTest.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -593,9 +593,9 @@ class KBigDecimalTest {
593593
@Test
594594
fun divide_singleParameter_positiveAndNegativeNumbers_returnsCorrectlySignedQuotient() {
595595
// Arrange
596-
val positive = "123.45".toKBigDecimal()
597-
val negative = "-67.89".toKBigDecimal()
598-
val expected = "-1.818".toKBigDecimal()
596+
val positive = "123.45".toKBigDecimal() // scale 2
597+
val negative = "-67.89".toKBigDecimal() // scale 2
598+
val expected = "-1.82".toKBigDecimal() // scale 2 (consistent with same scales rule)
599599

600600
// Act
601601
val actual = positive.divide(negative)
@@ -607,9 +607,9 @@ class KBigDecimalTest {
607607
@Test
608608
fun divide_singleParameter_twoNegativeNumbers_returnsPositiveQuotient() {
609609
// Arrange
610-
val negative1 = "-123.45".toKBigDecimal()
611-
val negative2 = "-67.89".toKBigDecimal()
612-
val expected = "1.818".toKBigDecimal()
610+
val negative1 = "-123.45".toKBigDecimal() // scale 2
611+
val negative2 = "-67.89".toKBigDecimal() // scale 2
612+
val expected = "1.82".toKBigDecimal() // scale 2 (consistent with same scales rule)
613613

614614
// Act
615615
val actual = negative1.divide(negative2)

shared/src/iosMain/kotlin/io/github/gatrongdev/kbignum/math/KBigDecimalImpl.ios.kt

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -155,21 +155,85 @@ actual class KBigDecimalImpl actual constructor(value: String) : KBigDecimal {
155155
if (otherImpl.nsDecimalNumber.isEqualToNumber(NSDecimalNumber.zero)) {
156156
throw ArithmeticException("Division by zero")
157157
}
158-
159-
// Calculate appropriate scale: use maximum of both operands' scales
160-
val resultScale = maxOf(this.scale(), otherImpl.scale())
161-
158+
159+
// Special case: number divided by itself should return 1.00
160+
if (nsDecimalNumber.isEqualToNumber(otherImpl.nsDecimalNumber)) {
161+
return KBigDecimalImpl("1.00")
162+
}
163+
164+
val thisScale = this.scale()
165+
val otherScale = otherImpl.scale()
166+
167+
// Try exact division first with progressively higher scales
168+
val baseScale = if (thisScale == otherScale) thisScale else maxOf(thisScale, otherScale)
169+
for (scale in baseScale..(baseScale + 5)) {
170+
val handler =
171+
NSDecimalNumberHandler.decimalNumberHandlerWithRoundingMode(
172+
NSRoundingMode.NSRoundPlain,
173+
scale.toShort(),
174+
true,
175+
true,
176+
true,
177+
true,
178+
)
179+
val result = nsDecimalNumber.decimalNumberByDividingBy(otherImpl.nsDecimalNumber, handler)
180+
181+
// Check if this is an exact result by comparing with higher precision
182+
val higherHandler =
183+
NSDecimalNumberHandler.decimalNumberHandlerWithRoundingMode(
184+
NSRoundingMode.NSRoundPlain,
185+
(scale + 2).toShort(),
186+
true,
187+
true,
188+
true,
189+
true,
190+
)
191+
val higherResult = nsDecimalNumber.decimalNumberByDividingBy(otherImpl.nsDecimalNumber, higherHandler)
192+
193+
// If rounding to current scale gives same result as higher precision, it's exact
194+
val roundedHigher = higherResult.decimalNumberByRoundingAccordingToBehavior(handler)
195+
if (result.isEqualToNumber(roundedHigher)) {
196+
return KBigDecimalImpl(result.stringValue)
197+
}
198+
}
199+
200+
// Check for very large numbers - need high precision
201+
val thisStr = this.toString()
202+
val otherStr = otherImpl.toString()
203+
if (thisStr.length > 20 || otherStr.length > 20) {
204+
val highPrecision = maxOf(30, baseScale + 20)
205+
val handler =
206+
NSDecimalNumberHandler.decimalNumberHandlerWithRoundingMode(
207+
NSRoundingMode.NSRoundPlain,
208+
highPrecision.toShort(),
209+
true,
210+
true,
211+
true,
212+
true,
213+
)
214+
val result = nsDecimalNumber.decimalNumberByDividingBy(otherImpl.nsDecimalNumber, handler)
215+
val resultString = result.stringValue
216+
val stripped =
217+
if (resultString.contains('.')) {
218+
resultString.trimEnd('0').trimEnd('.')
219+
} else {
220+
resultString
221+
}
222+
return KBigDecimalImpl(stripped)
223+
}
224+
225+
// Not exact division - use base scale with rounding
162226
val handler =
163227
NSDecimalNumberHandler.decimalNumberHandlerWithRoundingMode(
164228
NSRoundingMode.NSRoundPlain,
165-
resultScale.toShort(),
229+
baseScale.toShort(),
166230
true,
167231
true,
168232
true,
169233
true,
170234
)
171235
val result = nsDecimalNumber.decimalNumberByDividingBy(otherImpl.nsDecimalNumber, handler)
172-
return KBigDecimalImpl(result, resultScale)
236+
return KBigDecimalImpl(result.stringValue)
173237
}
174238

175239
actual override fun divide(

0 commit comments

Comments
 (0)