Skip to content

Commit 139e547

Browse files
authored
Merge pull request #34 from gatrongdev/copilot/fix-27
Add single-parameter divide function to KBigDecimal
2 parents 1195b98 + f1a5687 commit 139e547

File tree

7 files changed

+275
-1
lines changed

7 files changed

+275
-1
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ val sum = bigDecimal1 + bigDecimal2
5858
val difference = bigDecimal1 - bigDecimal2
5959
val product = bigDecimal1 * bigDecimal2
6060
val quotient = bigDecimal1.divide(bigDecimal2, 10) // 10 decimal places
61+
val simpleQuotient = bigDecimal1.divide(bigDecimal2) // uses automatic scale
6162

6263
// Advanced operations
6364
val sqrt = KBigMath.sqrt(bigDecimal1, 10)
@@ -102,6 +103,7 @@ Interface for arbitrary precision decimal numbers:
102103
- `add(other: KBigDecimal): KBigDecimal`
103104
- `subtract(other: KBigDecimal): KBigDecimal`
104105
- `multiply(other: KBigDecimal): KBigDecimal`
106+
- `divide(other: KBigDecimal): KBigDecimal`
105107
- `divide(other: KBigDecimal, scale: Int): KBigDecimal`
106108
- `abs(): KBigDecimal`
107109
- `signum(): Int`

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ plugins {
66

77
// Code quality and security plugins
88
id("org.jetbrains.kotlinx.kover") version "0.8.3" apply false
9-
id("org.jlleitschuh.gradle.ktlint") version "12.1.1" apply false
9+
id("org.jlleitschuh.gradle.ktlint") version "12.3.0" apply false
1010
id("io.gitlab.arturbosch.detekt") version "1.23.6" apply false
1111

1212
// Dokka plugin for documentation

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,45 @@ actual class KBigDecimalImpl actual constructor(value: String) : KBigDecimal {
3737
return KBigDecimalImpl(bigDecimal.multiply(otherImpl.bigDecimal).toString())
3838
}
3939

40+
actual override fun divide(other: KBigDecimal): KBigDecimal {
41+
val otherImpl = other as KBigDecimalImpl
42+
if (otherImpl.bigDecimal.signum() == 0) {
43+
throw ArithmeticException("Division by zero")
44+
}
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())
77+
}
78+
4079
actual override fun divide(
4180
other: KBigDecimal,
4281
scale: Int,

shared/src/commonMain/kotlin/io/github/gatrongdev/kbignum/math/KBigDecimal.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ interface KBigDecimal : Comparable<KBigDecimal> {
2626
*/
2727
fun multiply(other: KBigDecimal): KBigDecimal
2828

29+
/**
30+
* Returns a KBigDecimal that is the quotient of this divided by the specified value.
31+
* @param other The divisor
32+
* @return The result of the division with the maximum scale of the two operands
33+
* @throws ArithmeticException if other is zero
34+
*/
35+
fun divide(other: KBigDecimal): KBigDecimal
36+
2937
/**
3038
* Returns a KBigDecimal that is the quotient of this divided by the specified value.
3139
* @param other The divisor

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ expect class KBigDecimalImpl(value: String) : KBigDecimal {
1717

1818
override fun multiply(other: KBigDecimal): KBigDecimal
1919

20+
override fun divide(other: KBigDecimal): KBigDecimal
21+
2022
override fun divide(
2123
other: KBigDecimal,
2224
scale: Int,

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

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,143 @@ class KBigDecimalTest {
524524
assertEquals(expected, actual)
525525
}
526526

527+
// SINGLE-PARAMETER DIVIDE FUNCTION TESTS
528+
@Test
529+
fun divide_singleParameter_byIntegerDivisor_returnsCorrectQuotient() {
530+
// Arrange
531+
val dividend = "123.45".toKBigDecimal()
532+
val divisor = "2".toKBigDecimal()
533+
val expected = "61.725".toKBigDecimal()
534+
535+
// Act
536+
val actual = dividend.divide(divisor)
537+
538+
// Assert
539+
assertEquals(expected, actual)
540+
}
541+
542+
@Test
543+
fun divide_singleParameter_numberByItself_returnsOne() {
544+
// Arrange
545+
val number = "123.45".toKBigDecimal()
546+
val expected = "1.00".toKBigDecimal()
547+
548+
// Act
549+
val actual = number.divide(number)
550+
551+
// Assert
552+
assertEquals(expected, actual)
553+
}
554+
555+
@Test
556+
fun divide_singleParameter_numberByOne_returnsItself() {
557+
// Arrange
558+
val number = "123.45".toKBigDecimal()
559+
val one = "1".toKBigDecimal()
560+
561+
// Act
562+
val actual = number.divide(one)
563+
564+
// Assert
565+
assertEquals(number, actual)
566+
}
567+
568+
@Test
569+
fun divide_singleParameter_zeroByNumber_returnsZero() {
570+
// Arrange
571+
val zero = KBigDecimalFactory.ZERO
572+
val number = "123.45".toKBigDecimal()
573+
574+
// Act
575+
val actual = zero.divide(number)
576+
577+
// Assert
578+
assertTrue(actual.isZero())
579+
}
580+
581+
@Test
582+
fun divide_singleParameter_byZero_throwsArithmeticException() {
583+
// Arrange
584+
val number = "123.45".toKBigDecimal()
585+
val zero = KBigDecimalFactory.ZERO
586+
587+
// Act & Assert
588+
assertFailsWith<ArithmeticException> {
589+
number.divide(zero)
590+
}
591+
}
592+
593+
@Test
594+
fun divide_singleParameter_positiveAndNegativeNumbers_returnsCorrectlySignedQuotient() {
595+
// Arrange
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)
599+
600+
// Act
601+
val actual = positive.divide(negative)
602+
603+
// Assert
604+
assertEquals(expected.toString(), actual.toString())
605+
}
606+
607+
@Test
608+
fun divide_singleParameter_twoNegativeNumbers_returnsPositiveQuotient() {
609+
// Arrange
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)
613+
614+
// Act
615+
val actual = negative1.divide(negative2)
616+
617+
// Assert
618+
assertEquals(expected.toString(), actual.toString())
619+
}
620+
621+
@Test
622+
fun divide_singleParameter_withDifferentScales_usesMaxScale() {
623+
// Arrange
624+
val dividend = "123.4567".toKBigDecimal() // scale 4
625+
val divisor = "12.34".toKBigDecimal() // scale 2
626+
// Expected scale should be max(4, 2) = 4
627+
628+
// Act
629+
val actual = dividend.divide(divisor)
630+
631+
// Assert
632+
assertEquals(4, actual.scale())
633+
assertTrue(actual.toString().startsWith("10.00"))
634+
}
635+
636+
@Test
637+
fun divide_singleParameter_withSameScales_maintainsScale() {
638+
// Arrange
639+
val dividend = "123.45".toKBigDecimal() // scale 2
640+
val divisor = "12.34".toKBigDecimal() // scale 2
641+
// Expected scale should be max(2, 2) = 2
642+
643+
// Act
644+
val actual = dividend.divide(divisor)
645+
646+
// Assert
647+
assertEquals(2, actual.scale())
648+
assertTrue(actual.toString().startsWith("10.00"))
649+
}
650+
651+
@Test
652+
fun divide_singleParameter_veryLargeNumbers_handlesCorrectly() {
653+
// Arrange
654+
val dividend = "999999999999999999999999.123456789".toKBigDecimal()
655+
val divisor = "999999999999999999999999".toKBigDecimal()
656+
657+
// Act
658+
val actual = dividend.divide(divisor)
659+
660+
// Assert
661+
assertTrue(actual.toString().startsWith("1.000000000000000000000000123"))
662+
}
663+
527664
// SETSCALE FUNCTION TESTS
528665
@Test
529666
fun setScale_toIncreaseScale_padsWithZeros() {

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

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,92 @@ actual class KBigDecimalImpl actual constructor(value: String) : KBigDecimal {
150150
return resultImpl
151151
}
152152

153+
actual override fun divide(other: KBigDecimal): KBigDecimal {
154+
val otherImpl = other as KBigDecimalImpl
155+
if (otherImpl.nsDecimalNumber.isEqualToNumber(NSDecimalNumber.zero)) {
156+
throw ArithmeticException("Division by zero")
157+
}
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
226+
val handler =
227+
NSDecimalNumberHandler.decimalNumberHandlerWithRoundingMode(
228+
NSRoundingMode.NSRoundPlain,
229+
baseScale.toShort(),
230+
true,
231+
true,
232+
true,
233+
true,
234+
)
235+
val result = nsDecimalNumber.decimalNumberByDividingBy(otherImpl.nsDecimalNumber, handler)
236+
return KBigDecimalImpl(result.stringValue)
237+
}
238+
153239
actual override fun divide(
154240
other: KBigDecimal,
155241
scale: Int,

0 commit comments

Comments
 (0)