Skip to content

Commit 0b6a839

Browse files
authored
Add BigInt.from_digits (#16259)
1 parent 1545c78 commit 0b6a839

File tree

3 files changed

+96
-0
lines changed

3 files changed

+96
-0
lines changed

spec/std/big/big_int_spec.cr

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,41 @@ describe "BigInt" do
5959
BigInt.new(12.3).to_s.should eq("12")
6060
end
6161

62+
describe ".from_digits" do
63+
it "creates from digits" do
64+
BigInt.from_digits([] of Int32).should eq(0.to_big_i)
65+
BigInt.from_digits([3, 2, 1]).should eq(123.to_big_i)
66+
BigInt.from_digits({3, 2, 1}, base: 16).should eq(0x123.to_big_i)
67+
BigInt.from_digits((0xfa..0xff), base: 256).should eq(0xfffefdfcfbfa.to_big_i)
68+
BigInt.from_digits([UInt128::MAX, UInt128::MAX, UInt128::MAX, UInt128::MAX], base: 2.to_big_i ** 128).should eq(2.to_big_i ** 512 - 1)
69+
BigInt.from_digits(Deque{1, 0.to_big_i, 0, 1.to_big_i}, base: 2).should eq(9.to_big_i)
70+
end
71+
72+
it "raises for base less than 2" do
73+
[-1, 0, 1].each do |base|
74+
expect_raises(ArgumentError, "Invalid base #{base}") do
75+
BigInt.from_digits([1, 2, 3], base)
76+
end
77+
end
78+
end
79+
80+
it "raises for digits greater than base" do
81+
expect_raises(ArgumentError, "Invalid digit 2 for base 2") do
82+
BigInt.from_digits([1, 0, 2], 2)
83+
end
84+
85+
expect_raises(ArgumentError, "Invalid digit 10 for base 2") do
86+
BigInt.from_digits([1, 0, 10], 2)
87+
end
88+
end
89+
90+
it "raises for negative digits" do
91+
expect_raises(ArgumentError, "Invalid digit -1") do
92+
BigInt.from_digits([1, 2, -1])
93+
end
94+
end
95+
end
96+
6297
it "compares" do
6398
1.to_big_i.should eq(1.to_big_i)
6499
1.to_big_i.should eq(1)

src/big/big_int.cr

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,63 @@ struct BigInt < Int
141141
new(mpz)
142142
end
143143

144+
# Returns a number for given *digits* and *base*. The digits are expected as
145+
# an `Enumerable` with the least significant digit as the first element.
146+
#
147+
# *base* must not be less than 2.
148+
#
149+
# All digits must be within `0...base`.
150+
def self.from_digits(digits : Enumerable(Int), base : Int = 10) : self
151+
if base < 2
152+
raise ArgumentError.new("Invalid base #{base}")
153+
end
154+
155+
new do |mpz|
156+
multiplier = new(1)
157+
first_element = true
158+
159+
digits.each do |digit|
160+
if digit < 0
161+
raise ArgumentError.new("Invalid digit #{digit}")
162+
end
163+
164+
if digit >= base
165+
raise ArgumentError.new("Invalid digit #{digit} for base #{base}")
166+
end
167+
168+
# don't calculate multiplier upfront for the next digit
169+
# to avoid overflow at the last iteration
170+
if first_element
171+
first_element = false
172+
173+
# mpz = digit
174+
Int.primitive_ui_check(digit) do |ui, _, big_i|
175+
{
176+
ui: LibGMP.set_ui(mpz, {{ ui }}),
177+
big_i: LibGMP.set(mpz, {{ big_i }}),
178+
}
179+
end
180+
else
181+
# multiplier *= base
182+
Int.primitive_ui_check(base) do |ui, _, big_i|
183+
{
184+
ui: LibGMP.mul_ui(multiplier, multiplier, {{ ui }}),
185+
big_i: LibGMP.mul(multiplier, multiplier, {{ big_i }}),
186+
}
187+
end
188+
189+
# mpz += base * digits
190+
Int.primitive_ui_check(digit) do |ui, _, big_i|
191+
{
192+
ui: LibGMP.addmul_ui(mpz, multiplier, {{ ui }}),
193+
big_i: LibGMP.addmul(mpz, multiplier, {{ big_i }}),
194+
}
195+
end
196+
end
197+
end
198+
end
199+
end
200+
144201
def <=>(other : BigInt)
145202
LibGMP.cmp(mpz, other)
146203
end

src/big/lib_gmp.cr

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ lib LibGMP
6565

6666
# # I/O
6767

68+
fun set = __gmpz_set(rop : MPZ*, op : MPZ*)
6869
fun set_ui = __gmpz_set_ui(rop : MPZ*, op : UI)
6970
fun set_si = __gmpz_set_si(rop : MPZ*, op : SI)
7071
fun set_d = __gmpz_set_d(rop : MPZ*, op : Double)
@@ -90,6 +91,9 @@ lib LibGMP
9091
fun mul_si = __gmpz_mul_si(rop : MPZ*, op1 : MPZ*, op2 : SI)
9192
fun mul_ui = __gmpz_mul_ui(rop : MPZ*, op1 : MPZ*, op2 : UI)
9293

94+
fun addmul = __gmpz_addmul(rop : MPZ*, op1 : MPZ*, op2 : MPZ*)
95+
fun addmul_ui = __gmpz_addmul_ui(rop : MPZ*, op1 : MPZ*, op2 : UI)
96+
9397
fun fdiv_q = __gmpz_fdiv_q(rop : MPZ*, op1 : MPZ*, op2 : MPZ*)
9498
fun fdiv_q_ui = __gmpz_fdiv_q_ui(rop : MPZ*, op1 : MPZ*, op2 : UI)
9599

0 commit comments

Comments
 (0)