Skip to content

Commit c0d0f3a

Browse files
feat(core): convert to/from ByteArray
1 parent d5721ed commit c0d0f3a

12 files changed

Lines changed: 204 additions & 19 deletions

File tree

kbigint/src/androidMain/kotlin/io/github/observeroftime/kbigint/KBigInt.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ actual class KBigInt private constructor(private var value: BigInteger) : Compar
1414
actual constructor(number: Long) : this(number.toBigInteger())
1515

1616
/** Convert a [ByteArray] to a [KBigInt]. */
17-
@Suppress("unused")
18-
constructor(bytes: ByteArray) : this(BigInteger(bytes))
17+
actual constructor(bytes: ByteArray) : this(BigInteger(bytes))
1918

2019
@get:JvmName("signum")
2120
actual val sign: Int
@@ -115,6 +114,5 @@ actual class KBigInt private constructor(private var value: BigInteger) : Compar
115114
fun toDouble() = value.toDouble()
116115

117116
/** Convert the value to a [ByteArray]. */
118-
@Suppress("unused")
119-
fun toByteArray(): ByteArray = value.toByteArray()
117+
actual fun toByteArray(): ByteArray = value.toByteArray()
120118
}

kbigint/src/androidUnitTest/kotlin/io/github/observeroftime/kbigint/KBigIntTest.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ actual class KBigIntTest {
5252
@Test
5353
actual fun testInvert() {
5454
assertEquals(KBigInt(-2147483649L), long.inv())
55+
assertEquals(KBigInt(2147483649L), -long.inv())
5556
}
5657

5758
@Test
@@ -101,4 +102,13 @@ actual class KBigIntTest {
101102
assertEquals(OVER_MAX_INT, long.toLong())
102103
assertEquals(OVER_MAX_INT.toDouble(), long.toDouble())
103104
}
105+
106+
@Test
107+
actual fun testByteArray() {
108+
assertEquals(KBigInt(262146), KBigInt(byteArrayOf(4, 0, 2)))
109+
assertEquals(KBigInt(-6021), KBigInt(byteArrayOf(-24, 123)))
110+
111+
assertContentEquals(byteArrayOf(4, 0, 2), KBigInt(262146).toByteArray())
112+
assertContentEquals(byteArrayOf(-24, 123), KBigInt(-6021).toByteArray())
113+
}
104114
}

kbigint/src/commonMain/kotlin/io/github/observeroftime/kbigint/KBigInt.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ expect class KBigInt : Comparable<KBigInt> {
1111
/** Convert a [Long] to a [KBigInt]. */
1212
constructor(number: Long)
1313

14+
/** Convert a [ByteArray] to a [KBigInt]. */
15+
constructor(bytes: ByteArray)
16+
1417
/**
1518
* The sign of the value:
1619
*
@@ -71,6 +74,9 @@ expect class KBigInt : Comparable<KBigInt> {
7174
/** Get the absolute value. */
7275
fun abs(): KBigInt
7376

77+
/** Convert the value to a [ByteArray]. */
78+
fun toByteArray(): ByteArray
79+
7480
override operator fun compareTo(other: KBigInt): Int
7581

7682
override fun equals(other: Any?): Boolean

kbigint/src/commonTest/kotlin/io/github/observeroftime/kbigint/KBigIntTest.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ expect class KBigIntTest {
1818
fun testEquals()
1919
fun testHashCode()
2020
fun testToString()
21+
fun testByteArray()
2122
}

kbigint/src/javascript/kbigint-utils.mjs

Lines changed: 109 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,5 @@
11
// noinspection JSUnusedGlobalSymbols
22

3-
/**
4-
* @private
5-
* @param {bigint} n
6-
* @param {bigint} k
7-
* @returns {bigint}
8-
* @see https://stackoverflow.com/a/53684036/21974435
9-
*/
10-
function newtonRoot(n, k) {
11-
const x = ((n / k) + k) >> 1n;
12-
return k === x || k === (x - 1n) ? k : newtonRoot(n, x);
13-
}
143
/**
154
* Return the sign of the value.
165
*
@@ -64,3 +53,112 @@ export function cmp(a, b) {
6453
export function pow(value, n) {
6554
return value ** BigInt(n);
6655
}
56+
57+
/**
58+
* Convert a {@link BigInt} to an {@link Int8Array}.
59+
*
60+
* @param value {bigint}
61+
* @return {Int8Array}
62+
* @see https://coolaj86.com/articles/convert-js-bigints-to-typedarrays/
63+
*/
64+
export function toByteArray(value) {
65+
let hex = bnToHex(value);
66+
if (hex.length & 1) hex = '0' + hex;
67+
const len = hex.length >> 1;
68+
const arr = new Int8Array(len);
69+
for (let i = 0, j = 0; i < len; i += 1, j += 2) {
70+
arr[i] = parseInt(hex.slice(j, j + 2), 16);
71+
}
72+
return arr;
73+
}
74+
75+
/**
76+
* Convert a numeric {@link Array} to a {@link BigInt}.
77+
*
78+
* @param bytes {number[] | ArrayLike<number>}
79+
* @return {bigint}
80+
* @see https://coolaj86.com/articles/convert-js-bigints-to-typedarrays/
81+
*/
82+
export function fromByteArray(bytes) {
83+
const hex = new Array(bytes.length);
84+
Uint8Array.from(bytes).forEach(n => {
85+
const h = n.toString(16);
86+
hex.push(h.length & 1 ? '0' + h : h);
87+
});
88+
return hexToBn(hex.join(''));
89+
}
90+
91+
/**
92+
* @private
93+
* @param {bigint} n
94+
* @param {bigint} k
95+
* @returns {bigint}
96+
* @see https://stackoverflow.com/a/53684036/21974435
97+
*/
98+
function newtonRoot(n, k) {
99+
const x = ((n / k) + k) >> 1n;
100+
return k === x || k === (x - 1n) ? k : newtonRoot(n, x);
101+
}
102+
103+
/**
104+
* @private
105+
* @param str {string}
106+
* @return {string}
107+
*/
108+
function bitFlip(str) {
109+
return Array.from(str, i => i === '0' ? '1' : '0').join('')
110+
}
111+
112+
/**
113+
* @private
114+
* @param bn {bigint}
115+
* @return {bigint}
116+
* @see https://coolaj86.com/articles/convert-decimal-to-hex-with-js-bigints/
117+
*/
118+
function bitNot(bn) {
119+
let bin = (-bn).toString(2)
120+
while (bin.length % 8) bin = '0' + bin;
121+
const prefix = bin[0] === '1' && bin.slice(1).includes('1')
122+
? '11111111' : ''
123+
return BigInt('0b' + prefix + bitFlip(bin)) + 1n;
124+
}
125+
126+
/**
127+
* @private
128+
* @param hex {string}
129+
* @return {number}
130+
*/
131+
function highByte(hex) {
132+
return parseInt(hex.slice(0, 2), 16);
133+
}
134+
135+
/**
136+
* @private
137+
* @param hex {string}
138+
* @return {bigint}
139+
* @see https://coolaj86.com/articles/convert-hex-to-decimal-with-js-bigints/
140+
*/
141+
function hexToBn(hex) {
142+
if (hex.length & 1) hex = '0' + hex;
143+
let bn = BigInt('0x' + hex);
144+
if (highByte(hex) & 128) {
145+
// manually perform two's complement (flip bits, add one)
146+
// (because JS binary operators are incorrect for negatives)
147+
bn = -(BigInt('0b' + bitFlip(bn.toString(2))) + 1n);
148+
}
149+
return bn;
150+
}
151+
152+
/**
153+
* @private
154+
* @param bn {bigint}
155+
* @return {string}
156+
* @see https://coolaj86.com/articles/convert-decimal-to-hex-with-js-bigints/
157+
*/
158+
function bnToHex(bn) {
159+
const pos = bn >= 0n;
160+
let hex = (pos ? bn : bitNot(bn)).toString(16);
161+
if (hex.length & 1) hex = '0' + hex;
162+
if (pos && (highByte(hex) & 128)) hex = '00' + hex;
163+
return hex;
164+
}

kbigint/src/jsMain/kotlin/io/github/observeroftime/kbigint/KBigInt.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ actual class KBigInt private constructor(@JsExternalArgument private var value:
1414
@Suppress("NON_EXPORTABLE_TYPE")
1515
actual constructor(number: Long) : this(BigInt(number))
1616

17+
@JsName("fromBuffer")
18+
actual constructor(bytes: ByteArray) : this(BigInt(KBigIntUtils.fromByteArray(bytes)))
19+
1720
actual val sign: Int
1821
get() = KBigIntUtils.sign(value)
1922

@@ -153,4 +156,6 @@ actual class KBigInt private constructor(@JsExternalArgument private var value:
153156
fun toDouble() = toString().toDouble()
154157

155158
actual override fun toString() = value.toString()
159+
160+
actual fun toByteArray() = KBigIntUtils.toByteArray(value)
156161
}

kbigint/src/jsMain/kotlin/io/github/observeroftime/kbigint/KBigIntUtils.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ internal external object KBigIntUtils {
1212
fun sign(value: BigInt): Int
1313
fun cmp(a: BigInt, b: BigInt): Int
1414
fun pow(value: BigInt, n: Int): BigInt
15+
fun toByteArray(value: BigInt): ByteArray
16+
fun fromByteArray(bytes: ByteArray): BigInt
1517
}

kbigint/src/jsTest/kotlin/io/github/observeroftime/kbigint/KBigIntTest.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ actual class KBigIntTest {
5252
@Test
5353
actual fun testInvert() {
5454
assertEquals(KBigInt(-2147483649L), long.inv())
55+
assertEquals(KBigInt(2147483649L), -long.inv())
5556
}
5657

5758
@Test
@@ -103,4 +104,13 @@ actual class KBigIntTest {
103104
assertFailsWith(NumberFormatException::class) { long.toInt() }
104105
assertEquals(OVER_MAX_INT.toDouble(), long.toDouble())
105106
}
107+
108+
@Test
109+
actual fun testByteArray() {
110+
assertContentEquals(byteArrayOf(4, 0, 2), KBigInt(262146).toByteArray())
111+
assertContentEquals(byteArrayOf(-24, 123), KBigInt(-6021).toByteArray())
112+
113+
assertEquals(KBigInt(262146), KBigInt(byteArrayOf(4, 0, 2)))
114+
assertEquals(KBigInt(-6021), KBigInt(byteArrayOf(-24, 123)))
115+
}
106116
}

kbigint/src/jvmMain/kotlin/io/github/observeroftime/kbigint/KBigInt.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ actual class KBigInt private constructor(private var value: BigInteger) : Compar
1212
actual constructor(number: Long) : this(number.toBigInteger())
1313

1414
/** Convert a [ByteArray] to a [KBigInt]. */
15-
@Suppress("unused")
16-
constructor(bytes: ByteArray) : this(BigInteger(bytes))
15+
actual constructor(bytes: ByteArray) : this(BigInteger(bytes))
1716

1817
@get:JvmName("signum")
1918
actual val sign: Int
@@ -118,6 +117,5 @@ actual class KBigInt private constructor(private var value: BigInteger) : Compar
118117
*
119118
* @see [BigInteger.toByteArray]
120119
*/
121-
@Suppress("unused")
122-
fun toByteArray(): ByteArray = value.toByteArray()
120+
actual fun toByteArray(): ByteArray = value.toByteArray()
123121
}

kbigint/src/jvmTest/kotlin/io/github/observeroftime/kbigint/KBigIntTest.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ actual class KBigIntTest {
5252
@Test
5353
actual fun testInvert() {
5454
assertEquals(KBigInt(-2147483649L), long.inv())
55+
assertEquals(KBigInt(2147483649L), -long.inv())
5556
}
5657

5758
@Test
@@ -101,4 +102,13 @@ actual class KBigIntTest {
101102
assertEquals(OVER_MAX_INT, long.toLong())
102103
assertEquals(OVER_MAX_INT.toDouble(), long.toDouble())
103104
}
105+
106+
@Test
107+
actual fun testByteArray() {
108+
assertEquals(KBigInt(262146), KBigInt(byteArrayOf(4, 0, 2)))
109+
assertEquals(KBigInt(-6021), KBigInt(byteArrayOf(-24, 123)))
110+
111+
assertContentEquals(byteArrayOf(4, 0, 2), KBigInt(262146).toByteArray())
112+
assertContentEquals(byteArrayOf(-24, 123), KBigInt(-6021).toByteArray())
113+
}
104114
}

0 commit comments

Comments
 (0)