Skip to content

Commit 10ab0bb

Browse files
authored
Merge pull request #33 from ionspin/add-tofloat-todouble
Add floatValue, doubleValue to BigInteger
2 parents 0f43661 + b5dbcf0 commit 10ab0bb

File tree

15 files changed

+399
-8
lines changed

15 files changed

+399
-8
lines changed

CHANGELOG.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
## Descriptive changelog
22
(All dates are DD.MM.YYYY)
33

4-
##### 0.1.0-SNAPSHOT - 11.5.2019
5-
- Currently same as 0.9.0
4+
##### 0.1.0-SNAPSHOT - 2.6.2019
5+
- Added toFloat and toDouble to BigInteger and ModularBigInteger classes
6+
- Added BigInteger creation from Float and Double by using `tryFromFloat` and `tryFromDouble`, with optional exact
7+
parameter to preserve precision.
8+
- Added BigInteger comparison with Float and Double
69

710
##### 0.0.9 - 11.5.2019 Adding modular integer support, changing api
811
- Added modular integers - ModularBigInteger

bignum/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ kotlin {
131131
implementation(kotlin(Deps.Jvm.testJUnit))
132132
implementation(Deps.Jvm.oshi)
133133
implementation(Deps.Jvm.coroutinesTest)
134+
implementation(kotlin(Deps.Jvm.reflection))
134135
}
135136
}
136137
val jsMain by getting {

bignum/src/commonMain/kotlin/com/ionspin/kotlin/bignum/BigNumber.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ interface BigNumber<BigType> where BigType : BigNumber<BigType> {
4141
fun fromShort(short: Short): BigType
4242
fun fromByte(byte: Byte): BigType
4343
fun fromBigInteger(bigInteger: BigInteger) : BigType
44+
fun tryFromFloat(float : Float, exactRequired : Boolean = false) : BigType
45+
fun tryFromDouble(double : Double, exactRequired : Boolean = false) : BigType
4446
}
4547

4648
interface Util<BigType> {
@@ -147,6 +149,8 @@ internal interface NarrowingOperations<BigType> where BigType : BigNumber<BigTyp
147149
fun ulongValue(exactRequired : Boolean = false) : ULong
148150
fun ubyteValue(exactRequired : Boolean = false) : UByte
149151
fun ushortValue(exactRequired : Boolean = false) : UShort
152+
fun floatValue(exactRequired : Boolean = false) : Float
153+
fun doubleValue(exactRequired: Boolean = false) : Double
150154
}
151155

152156
@ExperimentalUnsignedTypes

bignum/src/commonMain/kotlin/com/ionspin/kotlin/bignum/decimal/BigDecimal.kt

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,12 @@ class BigDecimal private constructor(
412412
* @return BigDecimal representing input
413413
*/
414414
fun fromFloat(float: Float, decimalMode: DecimalMode? = null): BigDecimal {
415-
return parseStringWithMode(float.toString().dropLastWhile { it == '0' }, decimalMode)
415+
val floatString = float.toString()
416+
return if (floatString.contains('.')) {
417+
parseStringWithMode(floatString.dropLastWhile { it == '0' }, decimalMode)
418+
} else {
419+
parseStringWithMode(floatString, decimalMode)
420+
}
416421
}
417422

418423
/**
@@ -424,7 +429,12 @@ class BigDecimal private constructor(
424429
* @return BigDecimal representing input
425430
*/
426431
fun fromDouble(double: Double, decimalMode: DecimalMode? = null): BigDecimal {
427-
return parseStringWithMode(double.toString().dropLastWhile { it == '0' }, decimalMode)
432+
val doubleString = double.toString()
433+
return if (doubleString.contains('.')) {
434+
parseStringWithMode(doubleString.dropLastWhile { it == '0' }, decimalMode)
435+
} else {
436+
parseStringWithMode(doubleString, decimalMode)
437+
}
428438
}
429439

430440
/**
@@ -614,6 +624,14 @@ class BigDecimal private constructor(
614624
return fromByte(byte, null)
615625
}
616626

627+
override fun tryFromFloat(float: Float, exactRequired: Boolean): BigDecimal {
628+
return fromFloat(float, null)
629+
}
630+
631+
override fun tryFromDouble(double: Double, exactRequired: Boolean): BigDecimal {
632+
return fromDouble(double, null)
633+
}
634+
617635
override fun parseString(string: String, base: Int): BigDecimal {
618636
return parseStringWithMode(string, null)
619637
}

bignum/src/commonMain/kotlin/com/ionspin/kotlin/bignum/integer/BigInteger.kt

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import com.ionspin.kotlin.bignum.BigNumber
2121
import com.ionspin.kotlin.bignum.BitwiseCapable
2222
import com.ionspin.kotlin.bignum.CommonBigNumberOperations
2323
import com.ionspin.kotlin.bignum.NarrowingOperations
24+
import com.ionspin.kotlin.bignum.decimal.BigDecimal
2425
import com.ionspin.kotlin.bignum.modular.ModularBigInteger
2526
import kotlin.math.ceil
2627
import kotlin.math.floor
@@ -94,6 +95,15 @@ class BigInteger internal constructor(wordArray: WordArray, val sign: Sign) : Bi
9495
val LOG_10_OF_2 = log10(2.0)
9596

9697
override fun parseString(string: String, base: Int): BigInteger {
98+
val decimal = string.contains('.')
99+
if (decimal) {
100+
val bigDecimal = BigDecimal.parseString(string)
101+
val isActuallyDecimal = (bigDecimal - bigDecimal.floor()) > 0
102+
if (isActuallyDecimal) {
103+
throw NumberFormatException("Supplied string is decimal, which cannot be converted to BigInteger without precision loss.")
104+
}
105+
return bigDecimal.toBigInteger()
106+
}
97107
val signed = (string[0] == '-' || string[0] == '+')
98108
return if (signed) {
99109
if (string.length == 1) {
@@ -173,6 +183,30 @@ class BigInteger internal constructor(wordArray: WordArray, val sign: Sign) : Bi
173183
override fun fromShort(short: Short) = BigInteger(short)
174184
override fun fromByte(byte: Byte) = BigInteger(byte)
175185

186+
override fun tryFromFloat(float: Float, exactRequired: Boolean): BigInteger {
187+
val floatDecimalPart = float - floor(float)
188+
val bigDecimal = BigDecimal.fromFloat(floor(float), null)
189+
190+
if (exactRequired) {
191+
if (floatDecimalPart > 0) {
192+
throw ArithmeticException("Cant create BigInteger without precision loss, and exact value was required")
193+
}
194+
}
195+
return bigDecimal.toBigInteger()
196+
}
197+
198+
override fun tryFromDouble(double: Double, exactRequired: Boolean): BigInteger {
199+
val doubleDecimalPart = double - floor(double)
200+
val bigDecimal = BigDecimal.fromDouble(floor(double), null)
201+
202+
if (exactRequired) {
203+
if (doubleDecimalPart > 0) {
204+
throw ArithmeticException("Cant create BigInteger without precision loss, and exact value was required")
205+
}
206+
}
207+
return bigDecimal.toBigInteger()
208+
}
209+
176210
override fun max(first: BigInteger, second: BigInteger): BigInteger {
177211
return if (first > second) {
178212
first
@@ -544,6 +578,11 @@ class BigInteger internal constructor(wordArray: WordArray, val sign: Sign) : Bi
544578
}
545579

546580
override fun compareTo(other: Any): Int {
581+
if (other is Number) {
582+
if (ComparisonWorkaround.isSpecialHandlingForFloatNeeded(other)) {
583+
return javascriptNumberComparison(other)
584+
}
585+
}
547586
return when (other) {
548587
is BigInteger -> compare(other)
549588
is Long -> compare(fromLong(other))
@@ -554,11 +593,58 @@ class BigInteger internal constructor(wordArray: WordArray, val sign: Sign) : Bi
554593
is UInt -> compare(fromUInt(other))
555594
is UShort -> compare(fromUShort(other))
556595
is UByte -> compare(fromUByte(other))
596+
is Float -> compareFloatAndBigInt(other) { compare(it) }
597+
is Double -> compareDoubleAndBigInt(other) {compare(it) }
557598
else -> throw RuntimeException("Invalid comparison type for BigInteger: ${other::class.simpleName}")
558599
}
559600

560601
}
561602

603+
/**
604+
* Javascrpt doesn't have different types for float, integer, long, it's all just "number", so we need
605+
* to check if it's a decimal or integer number before comparing.
606+
*/
607+
private fun javascriptNumberComparison(number : Number) : Int {
608+
val float = number.toFloat()
609+
return when {
610+
float % 1 == 0f -> compare(fromLong(number.toLong()))
611+
else -> compareFloatAndBigInt(number.toFloat()) { compare(it) }
612+
613+
}
614+
}
615+
616+
fun compareFloatAndBigInt(float : Float, comparisonBlock : (BigInteger) -> Int) : Int {
617+
val withoutDecimalPart = floor(float)
618+
val hasDecimalPart = (float % 1 != 0f)
619+
return if (hasDecimalPart) {
620+
val comparisonResult = comparisonBlock.invoke(tryFromFloat(withoutDecimalPart + 1))
621+
if (comparisonResult == 0) {
622+
// They were equal with float incremented by one (because of decimal part) so the BigInt was larger
623+
1
624+
} else {
625+
comparisonResult
626+
}
627+
} else {
628+
comparisonBlock.invoke(tryFromFloat(withoutDecimalPart))
629+
}
630+
}
631+
632+
fun compareDoubleAndBigInt(double : Double, comparisonBlock : (BigInteger) -> Int) : Int {
633+
val withoutDecimalPart = floor(double)
634+
val hasDecimalPart = (double % 1 != 0.0)
635+
return if (hasDecimalPart) {
636+
val comparisonResult = comparisonBlock.invoke(tryFromDouble(withoutDecimalPart + 1))
637+
if (comparisonResult == 0) {
638+
// They were equal with double incremented by one (because of decimal part) so the BigInt was larger
639+
1
640+
} else {
641+
comparisonResult
642+
}
643+
} else {
644+
comparisonBlock.invoke(tryFromDouble(withoutDecimalPart))
645+
}
646+
}
647+
562648
override fun equals(other: Any?): Boolean {
563649
val comparison = when (other) {
564650
is BigInteger -> compare(other)
@@ -678,6 +764,20 @@ class BigInteger internal constructor(wordArray: WordArray, val sign: Sign) : Bi
678764
return magnitude[0].toUShort()
679765
}
680766

767+
override fun floatValue(exactRequired: Boolean): Float {
768+
if (exactRequired && this > Float.MAX_VALUE) {
769+
throw ArithmeticException("Cannot convert to float and provide exact value")
770+
}
771+
return this.toString().toFloat()
772+
}
773+
774+
override fun doubleValue(exactRequired: Boolean): Double {
775+
if (exactRequired && this > Double.MAX_VALUE) {
776+
throw ArithmeticException("Cannot convert to float and provide exact value")
777+
}
778+
return this.toString().toDouble()
779+
}
780+
681781
operator fun rangeTo(other : BigInteger) = BigIntegerRange(this, other)
682782

683783
class BigIntegerRange(override val start : BigInteger, override val endInclusive : BigInteger) : ClosedRange<BigInteger>, Iterable<BigInteger> {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2019 Ugljesa Jovanovic
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
package com.ionspin.kotlin.bignum.integer
19+
20+
/**
21+
* Created by Ugljesa Jovanovic
22+
23+
* on 02-Jun-2019
24+
*/
25+
expect object ComparisonWorkaround {
26+
/**
27+
* We need to know if we are running on a platform that doesn't know how to tell decimal and integer apart.
28+
*/
29+
fun isSpecialHandlingForFloatNeeded(number : Number) : Boolean
30+
}

bignum/src/commonMain/kotlin/com/ionspin/kotlin/bignum/integer/util/DigitUtil.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ fun Char.toDigit() : Int {
2929
in 'A' .. 'Z' -> this - 'A' + 10
3030
in '\uFF21' .. '\uFF3A' -> this - '\uFF21' - 10
3131
in '\uFF41' .. '\uFF5A' -> this - '\uFF41' - 10
32-
else -> throw NumberFormatException("Invalid digit for radix ")
32+
'.' -> throw NumberFormatException("Invalid digit for radix $this (Possibly a decimal value, which is not supported by BigInteger parser")
33+
else -> throw NumberFormatException("Invalid digit for radix $this")
3334
}
3435
}

bignum/src/commonMain/kotlin/com/ionspin/kotlin/bignum/modular/ModularBigInteger.kt

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,14 @@ class ModularBigInteger @ExperimentalUnsignedTypes constructor(
100100
return ModularBigInteger(BigInteger.fromByte(byte).prep(), modulo, this)
101101
}
102102

103+
override fun tryFromFloat(float: Float, exactRequired: Boolean): ModularBigInteger {
104+
return ModularBigInteger(BigInteger.tryFromFloat(float, exactRequired).prep(), modulo, this)
105+
}
106+
107+
override fun tryFromDouble(double: Double, exactRequired: Boolean): ModularBigInteger {
108+
return ModularBigInteger(BigInteger.tryFromDouble(double, exactRequired).prep(), modulo, this)
109+
}
110+
103111
private fun BigInteger.prep() : BigInteger {
104112
val result = this % modulo
105113
return when (result.sign) {
@@ -334,7 +342,11 @@ class ModularBigInteger @ExperimentalUnsignedTypes constructor(
334342
return residue.magnitude[0].toUShort()
335343
}
336344

345+
override fun floatValue(exactRequired: Boolean): Float {
346+
return residue.floatValue()
347+
}
337348

338-
339-
349+
override fun doubleValue(exactRequired: Boolean): Double {
350+
return residue.doubleValue()
351+
}
340352
}

bignum/src/commonTest/kotlin/com/ionspin/kotlin/bignum/integer/BigIntegerTest.kt

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,4 +363,48 @@ BigIntegerTest {
363363
}
364364
}
365365

366+
@Test
367+
fun doubleComparisonTest() {
368+
assertTrue {
369+
val a = 1.1
370+
val b = BigInteger.fromInt(1)
371+
b.compareTo(a) == -1
372+
}
373+
374+
assertTrue {
375+
val a = 0.9
376+
val b = BigInteger.fromInt(1)
377+
b.compareTo(a) == 1
378+
}
379+
380+
assertTrue {
381+
val a = 1.0
382+
val b = BigInteger.fromInt(1)
383+
b.compareTo(a) == 0
384+
}
385+
386+
}
387+
388+
@Test
389+
fun floatComparisonTest() {
390+
assertTrue {
391+
val a = 1.1f
392+
val b = BigInteger.fromInt(1)
393+
b.compareTo(a) == -1
394+
}
395+
396+
assertTrue {
397+
val a = 0.9f
398+
val b = BigInteger.fromInt(1)
399+
b.compareTo(a) == 1
400+
}
401+
402+
assertTrue {
403+
val a = 1.0f
404+
val b = BigInteger.fromInt(1)
405+
b.compareTo(a) == 0
406+
}
407+
408+
}
409+
366410
}

0 commit comments

Comments
 (0)