From ddae71eb8396ab60f455efbf26e95e8ff568fddd Mon Sep 17 00:00:00 2001 From: ZHU Yuhao Date: Wed, 25 Mar 2026 00:06:44 +0100 Subject: [PATCH 1/8] Fix bug in from int128 --- src/decimo/bigint/bigint.mojo | 35 ++++++++++++++++-------- tests/bigint/test_bigint_conversion.mojo | 32 ++++++++++++++++++++++ 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/src/decimo/bigint/bigint.mojo b/src/decimo/bigint/bigint.mojo index 78004389..0ffad457 100644 --- a/src/decimo/bigint/bigint.mojo +++ b/src/decimo/bigint/bigint.mojo @@ -293,26 +293,39 @@ struct BigInt( return Self() var sign: Bool - var magnitude: UInt64 + var remaining: Scalar[dtype] comptime if dtype.is_unsigned(): sign = False - magnitude = UInt64(value) + remaining = value else: if value < 0: sign = True - # Compute magnitude using explicit two's-complement conversion - magnitude = UInt64(0) - UInt64(value) + remaining = value else: sign = False - magnitude = UInt64(value) + remaining = value - var words = List[UInt32](capacity=2) - var lo = UInt32(magnitude & 0xFFFF_FFFF) - var hi = UInt32(magnitude >> 32) - words.append(lo) - if hi != 0: - words.append(hi) + var words = List[UInt32](capacity=4) + + comptime if dtype.is_unsigned(): + while remaining != 0: + words.append(UInt32(remaining & 0xFFFF_FFFF)) + remaining >>= 32 + else: + if sign: + # Extract words from negative value without casting to + # a wider unsigned type. We divide by -0x1_0000_0000 to + # extract each 32-bit chunk of the magnitude. + while remaining != 0: + var quotient = remaining // Scalar[dtype](-0x1_0000_0000) + var word_val = remaining % Scalar[dtype](-0x1_0000_0000) + words.append(UInt32(-word_val)) + remaining = -quotient + else: + while remaining != 0: + words.append(UInt32(remaining & 0xFFFF_FFFF)) + remaining >>= 32 return Self(raw_words=words^, sign=sign) diff --git a/tests/bigint/test_bigint_conversion.mojo b/tests/bigint/test_bigint_conversion.mojo index 4b1111a9..614772e2 100644 --- a/tests/bigint/test_bigint_conversion.mojo +++ b/tests/bigint/test_bigint_conversion.mojo @@ -106,6 +106,38 @@ def test_from_integral_scalar() raises: var i64 = BigInt(Int64(-9223372036854775808)) testing.assert_equal(String(i64), "-9223372036854775808") + # UInt128 + var u128_small = BigInt(UInt128(12345)) + testing.assert_equal(String(u128_small), "12345") + + var u128_large = BigInt(UInt128(80554649779790687400)) + testing.assert_equal(String(u128_large), "80554649779790687400") + + # UInt128.MAX = 340282366920938463463374607431768211455 + var u128_max = BigInt(UInt128.MAX) + testing.assert_equal( + String(u128_max), "340282366920938463463374607431768211455" + ) + + # Int128 + var i128_pos = BigInt(Int128(80554649779790687400)) + testing.assert_equal(String(i128_pos), "80554649779790687400") + + var i128_neg = BigInt(Int128(-80554649779790687400)) + testing.assert_equal(String(i128_neg), "-80554649779790687400") + + # Int128.MIN = -170141183460469231731687303715884105728 + var i128_min = BigInt(Int128.MIN) + testing.assert_equal( + String(i128_min), "-170141183460469231731687303715884105728" + ) + + # Int128.MAX = 170141183460469231731687303715884105727 + var i128_max = BigInt(Int128.MAX) + testing.assert_equal( + String(i128_max), "170141183460469231731687303715884105727" + ) + # ===----------------------------------------------------------------------=== # # Test: D&C from_string for large numbers From 3940eb749b8b87c61089d4f82807e71ede825474 Mon Sep 17 00:00:00 2001 From: ZHU Yuhao Date: Thu, 26 Mar 2026 00:47:09 +0100 Subject: [PATCH 2/8] Better code --- src/decimo/bigint/bigint.mojo | 152 ++++++++++++++++++++++++++++------ 1 file changed, 125 insertions(+), 27 deletions(-) diff --git a/src/decimo/bigint/bigint.mojo b/src/decimo/bigint/bigint.mojo index 0ffad457..4d7b64d3 100644 --- a/src/decimo/bigint/bigint.mojo +++ b/src/decimo/bigint/bigint.mojo @@ -28,6 +28,7 @@ in little-endian order, and a separate sign bit. """ from std.memory import UnsafePointer, memcpy +from std.sys import size_of import decimo.bigint.arithmetics import decimo.bigint.bitwise @@ -275,7 +276,10 @@ struct BigInt( @staticmethod def from_integral_scalar[dtype: DType, //](value: SIMD[dtype, 1]) -> Self: """Initializes a BigInt from an integral scalar. - This includes all SIMD integral types, such as Int8, Int16, UInt32, etc. + This includes all SIMD integral types: + Int8, Int16, Int32, Int64, Int128, + UInt8, UInt16, UInt32, UInt64, UInt128, + and the platform-sized Int (DType.int) and UInt (DType.uint). Constraints: The dtype must be integral. @@ -292,42 +296,136 @@ struct BigInt( if value == 0: return Self() - var sign: Bool - var remaining: Scalar[dtype] + # --- Unsigned types: direct word extraction via bit ops --- - comptime if dtype.is_unsigned(): - sign = False - remaining = value - else: - if value < 0: - sign = True - remaining = value - else: - sign = False - remaining = value + comptime if dtype == DType.uint8 or dtype == DType.uint16: + # Fits in 1 word + return Self(raw_words=[UInt32(value)], sign=False) - var words = List[UInt32](capacity=4) + elif dtype == DType.uint32: + return Self(raw_words=[UInt32(value)], sign=False) - comptime if dtype.is_unsigned(): + elif dtype == DType.uint64: + var words = List[UInt32](capacity=2) + words.append(UInt32(value & 0xFFFF_FFFF)) + var hi = UInt32(value >> 32) + if hi != 0: + words.append(hi) + return Self(raw_words=words^, sign=False) + + elif dtype == DType.uint128: + var words = List[UInt32](capacity=4) + var remaining = value while remaining != 0: words.append(UInt32(remaining & 0xFFFF_FFFF)) remaining >>= 32 - else: + return Self(raw_words=words^, sign=False) + + # --- Platform-sized UInt (pointer width, 32- or 64-bit) --- + + elif dtype == DType.uint: + comptime if size_of[Scalar[DType.uint]]() == 4: + # 32-bit platform: same as uint32 + return Self(raw_words=[UInt32(value)], sign=False) + elif size_of[Scalar[DType.uint]]() == 8: + # 64-bit platform: same as uint64 + var words = List[UInt32](capacity=2) + words.append(UInt32(value & 0xFFFF_FFFF)) + var hi = UInt32(value >> 32) + if hi != 0: + words.append(hi) + return Self(raw_words=words^, sign=False) + else: + comptime assert False, "unsupported platform UInt size" + + # --- Signed types <= 64 bits: convert magnitude to UInt64 --- + + elif dtype == DType.int8 or dtype == DType.int16: + # Magnitude fits in 1 word + if value < 0: + return Self(raw_words=[UInt32(-Int32(value))], sign=True) + else: + return Self(raw_words=[UInt32(value)], sign=False) + + elif dtype == DType.int32: + if value < 0: + var magnitude = UInt64(0) - UInt64(value) + var words = List[UInt32](capacity=2) + words.append(UInt32(magnitude & 0xFFFF_FFFF)) + var hi = UInt32(magnitude >> 32) + if hi != 0: + words.append(hi) + return Self(raw_words=words^, sign=True) + else: + return Self(raw_words=[UInt32(value)], sign=False) + + elif dtype == DType.int64: + var sign = value < 0 + var magnitude: UInt64 if sign: - # Extract words from negative value without casting to - # a wider unsigned type. We divide by -0x1_0000_0000 to - # extract each 32-bit chunk of the magnitude. - while remaining != 0: - var quotient = remaining // Scalar[dtype](-0x1_0000_0000) - var word_val = remaining % Scalar[dtype](-0x1_0000_0000) + magnitude = UInt64(0) - UInt64(value) + else: + magnitude = UInt64(value) + var words = List[UInt32](capacity=2) + words.append(UInt32(magnitude & 0xFFFF_FFFF)) + var hi = UInt32(magnitude >> 32) + if hi != 0: + words.append(hi) + return Self(raw_words=words^, sign=sign) + + # --- Platform-sized Int (pointer width, 32- or 64-bit) --- + + elif dtype == DType.int: + comptime if size_of[Scalar[DType.int]]() == 4: + # 32-bit platform: same as int32 + if value < 0: + var magnitude = UInt64(0) - UInt64(value) + var words = List[UInt32](capacity=2) + words.append(UInt32(magnitude & 0xFFFF_FFFF)) + var hi = UInt32(magnitude >> 32) + if hi != 0: + words.append(hi) + return Self(raw_words=words^, sign=True) + else: + return Self(raw_words=[UInt32(value)], sign=False) + elif size_of[Scalar[DType.int]]() == 8: + # 64-bit platform: same as int64 + var sign = value < 0 + var magnitude: UInt64 + if sign: + magnitude = UInt64(0) - UInt64(value) + else: + magnitude = UInt64(value) + var words = List[UInt32](capacity=2) + words.append(UInt32(magnitude & 0xFFFF_FFFF)) + var hi = UInt32(magnitude >> 32) + if hi != 0: + words.append(hi) + return Self(raw_words=words^, sign=sign) + else: + comptime assert False, "unsupported platform Int size" + + # --- Int128: use division to extract 32-bit chunks --- + + elif dtype == DType.int128: + var sign = value < 0 + var words = List[UInt32](capacity=4) + var rem = Int128(value) + if sign: + while rem != 0: + var quotient = rem // Int128(-0x1_0000_0000) + var word_val = rem % Int128(-0x1_0000_0000) words.append(UInt32(-word_val)) - remaining = -quotient + rem = -quotient else: - while remaining != 0: - words.append(UInt32(remaining & 0xFFFF_FFFF)) - remaining >>= 32 + while rem != 0: + words.append(UInt32(rem & 0xFFFF_FFFF)) + rem >>= 32 + return Self(raw_words=words^, sign=sign) - return Self(raw_words=words^, sign=sign) + else: + comptime assert False, "unsupported integral dtype" + return Self() @staticmethod def from_string(value: String) raises -> Self: From 030d2aea2cea44612e87ad2836e32eae5abd24c1 Mon Sep 17 00:00:00 2001 From: ZHU Yuhao Date: Thu, 26 Mar 2026 00:57:16 +0100 Subject: [PATCH 3/8] Add 256-bit --- src/decimo/bigint/bigint.mojo | 82 ++++++++++++------------------ src/decimo/bigint/exponential.mojo | 4 +- 2 files changed, 34 insertions(+), 52 deletions(-) diff --git a/src/decimo/bigint/bigint.mojo b/src/decimo/bigint/bigint.mojo index 4d7b64d3..04eda809 100644 --- a/src/decimo/bigint/bigint.mojo +++ b/src/decimo/bigint/bigint.mojo @@ -180,7 +180,7 @@ struct BigInt( self = Self.from_string(value) @implicit - def __init__(out self, value: Scalar): + def __init__(out self, value: Scalar) where value.dtype.is_integral(): """Constructs a BigInt from an integral scalar. This includes all SIMD integral types, such as Int8, Int16, UInt32, etc. @@ -231,54 +231,13 @@ struct BigInt( return Self(raw_words=words^, sign=sign) @staticmethod - def from_uint64(value: UInt64) -> Self: - """Creates a BigInt from a UInt64. - - Args: - value: The unsigned 64-bit integer value. - - Returns: - The BigInt representation. - """ - if value == 0: - return Self() - - var words = List[UInt32](capacity=2) - var lo = UInt32(value & 0xFFFF_FFFF) - var hi = UInt32(value >> 32) - words.append(lo) - if hi != 0: - words.append(hi) - - return Self(raw_words=words^, sign=False) - - @staticmethod - def from_uint128(value: UInt128) -> Self: - """Creates a BigInt from a UInt128. - - Args: - value: The unsigned 128-bit integer value. - - Returns: - The BigInt representation. - """ - if value == 0: - return Self() - - var words = List[UInt32](capacity=4) - var remaining = value - while remaining != 0: - words.append(UInt32(remaining & 0xFFFF_FFFF)) - remaining >>= 32 - - return Self(raw_words=words^, sign=False) - - @staticmethod - def from_integral_scalar[dtype: DType, //](value: SIMD[dtype, 1]) -> Self: + def from_integral_scalar[ + dtype: DType, // + ](value: SIMD[dtype, 1]) -> Self where dtype.is_integral(): """Initializes a BigInt from an integral scalar. This includes all SIMD integral types: - Int8, Int16, Int32, Int64, Int128, - UInt8, UInt16, UInt32, UInt64, UInt128, + Int8, Int16, Int32, Int64, Int128, Int256, + UInt8, UInt16, UInt32, UInt64, UInt128, UInt256, and the platform-sized Int (DType.int) and UInt (DType.uint). Constraints: @@ -291,8 +250,6 @@ struct BigInt( The BigInt representation of the Scalar value. """ - comptime assert dtype.is_integral(), "dtype must be integral." - if value == 0: return Self() @@ -321,6 +278,14 @@ struct BigInt( remaining >>= 32 return Self(raw_words=words^, sign=False) + elif dtype == DType.uint256: + var words = List[UInt32](capacity=8) + var remaining = value + while remaining != 0: + words.append(UInt32(remaining & 0xFFFF_FFFF)) + remaining >>= 32 + return Self(raw_words=words^, sign=False) + # --- Platform-sized UInt (pointer width, 32- or 64-bit) --- elif dtype == DType.uint: @@ -423,9 +388,26 @@ struct BigInt( rem >>= 32 return Self(raw_words=words^, sign=sign) + # --- Int256: use division to extract 32-bit chunks --- + + elif dtype == DType.int256: + var sign = value < 0 + var words = List[UInt32](capacity=8) + var rem = Int256(value) + if sign: + while rem != 0: + var quotient = rem // Int256(-0x1_0000_0000) + var word_val = rem % Int256(-0x1_0000_0000) + words.append(UInt32(-word_val)) + rem = -quotient + else: + while rem != 0: + words.append(UInt32(rem & 0xFFFF_FFFF)) + rem >>= 32 + return Self(raw_words=words^, sign=sign) + else: comptime assert False, "unsupported integral dtype" - return Self() @staticmethod def from_string(value: String) raises -> Self: diff --git a/src/decimo/bigint/exponential.mojo b/src/decimo/bigint/exponential.mojo index 5571ba73..9671b867 100644 --- a/src/decimo/bigint/exponential.mojo +++ b/src/decimo/bigint/exponential.mojo @@ -315,7 +315,7 @@ def sqrt(x: BigInt) raises -> BigInt: guess -= 1 while (guess + 1) * (guess + 1) <= val: guess += 1 - return BigInt.from_uint64(guess) + return BigInt.from_integral_scalar(guess) # For all larger inputs: optimized precision-doubling with UInt64 fast path return _sqrt_precision_doubling_fast(x) @@ -400,7 +400,7 @@ def _sqrt_precision_doubling_fast(x: BigInt) raises -> BigInt: ) if decimo.bigint.arithmetics._compare_word_lists(a_sq, x.words) > 0: a_val -= 1 - return BigInt.from_uint64(a_val) + return BigInt.from_integral_scalar(a_val) # --- Phase 1.5: UInt128 arithmetic for 1-2 more iterations --- # Extends the native phase to cover e+d up to ~126 bits, avoiding From 01eaebc20e6b6fc79c0f1d22216eb3c2eccc5bcd Mon Sep 17 00:00:00 2001 From: ZHU Yuhao Date: Thu, 26 Mar 2026 00:58:18 +0100 Subject: [PATCH 4/8] Update int for 32-bit and 64-bit --- src/decimo/bigint/bigint.mojo | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/decimo/bigint/bigint.mojo b/src/decimo/bigint/bigint.mojo index 04eda809..2bd678e5 100644 --- a/src/decimo/bigint/bigint.mojo +++ b/src/decimo/bigint/bigint.mojo @@ -221,14 +221,19 @@ struct BigInt( sign = False magnitude = UInt(value) - # Split the magnitude into 32-bit words - # On 64-bit platforms, Int is 64 bits → at most 2 words - var words = List[UInt32](capacity=2) - while magnitude != 0: + comptime if size_of[Int]() == 4: + # 32-bit platform: magnitude fits in 1 word + return Self(raw_words=[UInt32(magnitude)], sign=sign) + elif size_of[Int]() == 8: + # 64-bit platform: at most 2 words + var words = List[UInt32](capacity=2) words.append(UInt32(magnitude & 0xFFFF_FFFF)) - magnitude >>= 32 - - return Self(raw_words=words^, sign=sign) + var hi = UInt32(magnitude >> 32) + if hi != 0: + words.append(hi) + return Self(raw_words=words^, sign=sign) + else: + comptime assert False, "unsupported platform Int size" @staticmethod def from_integral_scalar[ From 2513e9dabc93f82760bb529188927c39681f7c59 Mon Sep 17 00:00:00 2001 From: ZHU Yuhao Date: Thu, 26 Mar 2026 00:59:45 +0100 Subject: [PATCH 5/8] Add tests --- tests/bigint/test_bigint_conversion.mojo | 57 ++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tests/bigint/test_bigint_conversion.mojo b/tests/bigint/test_bigint_conversion.mojo index 614772e2..22f380e0 100644 --- a/tests/bigint/test_bigint_conversion.mojo +++ b/tests/bigint/test_bigint_conversion.mojo @@ -138,6 +138,63 @@ def test_from_integral_scalar() raises: String(i128_max), "170141183460469231731687303715884105727" ) + # UInt256 + var u256_small = BigInt(UInt256(12345)) + testing.assert_equal(String(u256_small), "12345") + + var u256_large = BigInt(UInt256(80554649779790687400)) + testing.assert_equal(String(u256_large), "80554649779790687400") + + # UInt256 value larger than UInt128.MAX + var u256_big = BigInt(UInt256(8055464977979068740023761289648172697)) + testing.assert_equal( + String(u256_big), "8055464977979068740023761289648172697" + ) + + # Int256 + var i256_pos = BigInt(Int256(8055464977979068740023761289648172697)) + testing.assert_equal( + String(i256_pos), "8055464977979068740023761289648172697" + ) + + var i256_neg = BigInt(Int256(-8055464977979068740023761289648172697)) + testing.assert_equal( + String(i256_neg), "-8055464977979068740023761289648172697" + ) + + # Int256.MIN + var i256_min = BigInt(Int256.MIN) + testing.assert_equal( + String(i256_min), + "-57896044618658097711785492504343953926634992332820282019728792003956564819968", + ) + + # Int256.MAX + var i256_max = BigInt(Int256.MAX) + testing.assert_equal( + String(i256_max), + "57896044618658097711785492504343953926634992332820282019728792003956564819967", + ) + + # Platform-sized UInt + var u_plat = BigInt(UInt(18446744073709551615)) + testing.assert_equal(String(u_plat), "18446744073709551615") + + # Platform-sized Int + var i_plat_pos = BigInt(Scalar[DType.int](1234567890)) + testing.assert_equal(String(i_plat_pos), "1234567890") + + var i_plat_neg = BigInt(Scalar[DType.int](-1234567890)) + testing.assert_equal(String(i_plat_neg), "-1234567890") + + # Zero for various types + testing.assert_equal(String(BigInt(UInt8(0))), "0") + testing.assert_equal(String(BigInt(Int32(0))), "0") + testing.assert_equal(String(BigInt(UInt64(0))), "0") + testing.assert_equal(String(BigInt(Int128(0))), "0") + testing.assert_equal(String(BigInt(UInt256(0))), "0") + testing.assert_equal(String(BigInt(Int256(0))), "0") + # ===----------------------------------------------------------------------=== # # Test: D&C from_string for large numbers From ae158d4701b07f2bb731f45ade41030d1eefb94b Mon Sep 17 00:00:00 2001 From: ZHU Yuhao Date: Thu, 26 Mar 2026 01:11:31 +0100 Subject: [PATCH 6/8] Use unsigned subtraction --- src/decimo/bigint/bigint.mojo | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/decimo/bigint/bigint.mojo b/src/decimo/bigint/bigint.mojo index 2bd678e5..1b11d69d 100644 --- a/src/decimo/bigint/bigint.mojo +++ b/src/decimo/bigint/bigint.mojo @@ -211,12 +211,7 @@ struct BigInt( if value < 0: sign = True - # Handle Int.MIN (two's complement asymmetry) - if value == Int.MIN: - # |Int.MIN| = Int.MAX + 1 - magnitude = UInt(Int.MAX) + 1 - else: - magnitude = UInt(-value) + magnitude = UInt(0) - UInt(value) else: sign = False magnitude = UInt(value) From 0d4188086653acd72a2c3de69c20d0377b464068 Mon Sep 17 00:00:00 2001 From: ZHU Yuhao Date: Thu, 26 Mar 2026 01:13:20 +0100 Subject: [PATCH 7/8] Add test of edge cases --- tests/bigint/test_bigint_conversion.mojo | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/bigint/test_bigint_conversion.mojo b/tests/bigint/test_bigint_conversion.mojo index 22f380e0..eda076bb 100644 --- a/tests/bigint/test_bigint_conversion.mojo +++ b/tests/bigint/test_bigint_conversion.mojo @@ -67,6 +67,32 @@ def test_to_int_overflow() raises: testing.assert_true(raised, "to_int should raise for very large number") +# ===----------------------------------------------------------------------=== # +# Test: from_int edge cases (Int.MIN, Int.MAX) +# ===----------------------------------------------------------------------=== # + + +def test_from_int_edge_cases() raises: + """Test from_int with Int.MIN, Int.MAX, and other edge values.""" + # Int.MAX via from_int path + var max_val = BigInt(Int.MAX) + testing.assert_equal(String(max_val), "9223372036854775807") + testing.assert_equal(Int(max_val), Int.MAX) + + # Int.MIN via from_int path — the critical edge case + var min_val = BigInt(Int.MIN) + testing.assert_equal(String(min_val), "-9223372036854775808") + testing.assert_equal(Int(min_val), Int.MIN) + + # -1 via from_int + var neg_one = BigInt(-1) + testing.assert_equal(String(neg_one), "-1") + + # 0 via from_int + var zero = BigInt(Int(0)) + testing.assert_equal(String(zero), "0") + + # ===----------------------------------------------------------------------=== # # Test: from_integral_scalar / Scalar constructor # ===----------------------------------------------------------------------=== # From d7b63b08dafbec335e8c4b1daa61f115607e81fe Mon Sep 17 00:00:00 2001 From: ZHU Yuhao Date: Thu, 26 Mar 2026 17:35:42 +0100 Subject: [PATCH 8/8] Update --- tests/bigint/test_bigint_conversion.mojo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/bigint/test_bigint_conversion.mojo b/tests/bigint/test_bigint_conversion.mojo index eda076bb..3df9e2cc 100644 --- a/tests/bigint/test_bigint_conversion.mojo +++ b/tests/bigint/test_bigint_conversion.mojo @@ -171,7 +171,7 @@ def test_from_integral_scalar() raises: var u256_large = BigInt(UInt256(80554649779790687400)) testing.assert_equal(String(u256_large), "80554649779790687400") - # UInt256 value larger than UInt128.MAX + # UInt256 value larger than UInt64.MAX var u256_big = BigInt(UInt256(8055464977979068740023761289648172697)) testing.assert_equal( String(u256_big), "8055464977979068740023761289648172697"