Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve conversions from BigInt to Int::Primitive #13562

Merged
merged 3 commits into from
Jun 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 98 additions & 78 deletions spec/std/big/big_int_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -498,98 +498,118 @@ describe "BigInt" do
a.should eq(b)
end

it "can be casted into other Number types" do
big = BigInt.new(1234567890)
big.to_i.should eq(1234567890)
big.to_i8!.should eq(-46)
big.to_i16!.should eq(722)
big.to_i32.should eq(1234567890)
big.to_i64.should eq(1234567890)
big.to_u.should eq(1234567890)
big.to_u8!.should eq(210)
big.to_u16!.should eq(722)
big.to_u32.should eq(1234567890)

expect_raises(OverflowError) { BigInt.new(-1234567890).to_u }

u64 = big.to_u64
u64.should eq(1234567890)
u64.should be_a(UInt64)
end

context "conversion to 64-bit" do
it "above 64 bits" do
big = BigInt.new("9" * 20)
expect_raises(OverflowError) { big.to_i64 }
expect_raises(OverflowError) { big.to_u64 }
big.to_i64!.should eq(7766279631452241919) # 99999999999999999999 - 5*(2**64)
big.to_u64!.should eq(7766279631452241919)

big = BigInt.new("9" * 32)
expect_raises(OverflowError) { big.to_i64 }
expect_raises(OverflowError) { big.to_u64 }
big.to_i64!.should eq(-8814407033341083649) # 99999999999999999999999999999999 - 5421010862428*(2**64)
big.to_u64!.should eq(9632337040368467967u64) # 99999999999999999999999999999999 - 5421010862427*(2**64)
describe "#to_i" do
it "converts to Int32" do
BigInt.new(1234567890).to_i.should(be_a(Int32)).should eq(1234567890)
expect_raises(OverflowError) { BigInt.new(2147483648).to_i }
expect_raises(OverflowError) { BigInt.new(-2147483649).to_i }
end
end

it "between 63 and 64 bits" do
big = BigInt.new(i = 9999999999999999999u64)
expect_raises(OverflowError) { big.to_i64 }
big.to_u64.should eq(i)
big.to_i64!.should eq(-8446744073709551617) # 9999999999999999999 - 2**64
big.to_u64!.should eq(i)
describe "#to_i!" do
it "converts to Int32" do
BigInt.new(1234567890).to_i!.should(be_a(Int32)).should eq(1234567890)
BigInt.new(2147483648).to_i!.should eq(Int32::MIN)
BigInt.new(-2147483649).to_i!.should eq(Int32::MAX)
end
end

it "between 32 and 63 bits" do
big = BigInt.new(i = 9999999999999)
big.to_i64.should eq(i)
big.to_u64.should eq(i)
big.to_i64!.should eq(i)
big.to_u64!.should eq(i)
describe "#to_u" do
it "converts to UInt32" do
BigInt.new(1234567890).to_u.should(be_a(UInt32)).should eq(1234567890_u32)
expect_raises(OverflowError) { BigInt.new(4294967296).to_u }
expect_raises(OverflowError) { BigInt.new(-1).to_u }
end
end

it "negative under 32 bits" do
big = BigInt.new(i = -9999)
big.to_i64.should eq(i)
expect_raises(OverflowError) { big.to_u64 }
big.to_i64!.should eq(i)
big.to_u64!.should eq(18446744073709541617u64) # -9999 + 2**64
describe "#to_u!" do
it "converts to UInt32" do
BigInt.new(1234567890).to_u!.should(be_a(UInt32)).should eq(1234567890_u32)
BigInt.new(4294967296).to_u!.should eq(0_u32)
BigInt.new(-1).to_u!.should eq(UInt32::MAX)
end
end

{% for n in [8, 16, 32, 64, 128] %}
describe "#to_u{{n}}" do
it "converts to UInt{{n}}" do
(0..{{n - 1}}).each do |i|
(1.to_big_i << i).to_u{{n}}.should eq(UInt{{n}}.new(1) << i)
end

UInt{{n}}::MIN.to_big_i.to_u{{n}}.should eq(UInt{{n}}::MIN)
UInt{{n}}::MAX.to_big_i.to_u{{n}}.should eq(UInt{{n}}::MAX)
end

it "negative between 32 and 63 bits" do
big = BigInt.new(i = -9999999999999)
big.to_i64.should eq(i)
expect_raises(OverflowError) { big.to_u64 }
big.to_i64!.should eq(i)
big.to_u64!.should eq(18446734073709551617u64) # -9999999999999 + 2**64
it "raises OverflowError" do
expect_raises(OverflowError) { (1.to_big_i << {{n}}).to_u{{n}} }
expect_raises(OverflowError) { (-1.to_big_i).to_u{{n}} }
expect_raises(OverflowError) { (-1.to_big_i << {{n}}).to_u{{n}} }
end
end

it "negative between 63 and 64 bits" do
big = BigInt.new("-9999999999999999999")
expect_raises(OverflowError) { big.to_i64 }
expect_raises(OverflowError) { big.to_u64 }
big.to_i64!.should eq(8446744073709551617) # -9999999999999999999 + 2**64
big.to_u64!.should eq(8446744073709551617)
describe "#to_i{{n}}" do
it "converts to Int{{n}}" do
(0..{{n - 2}}).each do |i|
(1.to_big_i << i).to_i{{n}}.should eq(Int{{n}}.new(1) << i)
(-1.to_big_i << i).to_i{{n}}.should eq(Int{{n}}.new(-1) << i)
end

Int{{n}}.zero.to_big_i.to_i{{n}}.should eq(Int{{n}}.zero)
Int{{n}}::MAX.to_big_i.to_i{{n}}.should eq(Int{{n}}::MAX)
Int{{n}}::MIN.to_big_i.to_i{{n}}.should eq(Int{{n}}::MIN)
end

it "raises OverflowError" do
expect_raises(OverflowError) { (Int{{n}}::MAX.to_big_i + 1).to_i{{n}} }
expect_raises(OverflowError) { (Int{{n}}::MIN.to_big_i - 1).to_i{{n}} }
expect_raises(OverflowError) { (1.to_big_i << {{n}}).to_i{{n}} }
expect_raises(OverflowError) { (-1.to_big_i << {{n}}).to_i{{n}} }
end
end

it "negative above 64 bits" do
big = BigInt.new("-" + "9" * 20)
expect_raises(OverflowError) { big.to_i64 }
expect_raises(OverflowError) { big.to_u64 }
big.to_i64!.should eq(-7766279631452241919) # -9999999999999999999 + 5*(2**64)
big.to_u64!.should eq(10680464442257309697u64) # -9999999999999999999 + 6*(2**64)

big = BigInt.new("-" + "9" * 32)
expect_raises(OverflowError) { big.to_i64 }
expect_raises(OverflowError) { big.to_u64 }
big.to_i64!.should eq(8814407033341083649) # -99999999999999999999999999999999 + 5421010862428*(2**64)
big.to_u64!.should eq(8814407033341083649)
describe "#to_u{{n}}!" do
it "converts to UInt{{n}}" do
(0..{{n - 1}}).each do |i|
(1.to_big_i << i).to_u{{n}}!.should eq(UInt{{n}}.new(1) << i)
end

UInt{{n}}::MAX.to_big_i.to_u{{n}}!.should eq(UInt{{n}}::MAX)
end

it "converts modulo (2 ** {{n}})" do
(1.to_big_i << {{n}}).to_u{{n}}!.should eq(UInt{{n}}.new(0))
(-1.to_big_i).to_u{{n}}!.should eq(UInt{{n}}::MAX)
(-1.to_big_i << {{n}}).to_u{{n}}!.should eq(UInt{{n}}.new(0))
(123.to_big_i - (1.to_big_i << {{n}})).to_u{{n}}!.should eq(UInt{{n}}.new(123))
(123.to_big_i + (1.to_big_i << {{n}})).to_u{{n}}!.should eq(UInt{{n}}.new(123))
(123.to_big_i - (1.to_big_i << {{n + 2}})).to_u{{n}}!.should eq(UInt{{n}}.new(123))
(123.to_big_i + (1.to_big_i << {{n + 2}})).to_u{{n}}!.should eq(UInt{{n}}.new(123))
end
end
end

it "can cast UInt64::MAX to UInt64 (#2264)" do
BigInt.new(UInt64::MAX).to_u64.should eq(UInt64::MAX)
end
describe "#to_i{{n}}!" do
it "converts to Int{{n}}" do
(0..126).each do |i|
(1.to_big_i << i).to_i{{n}}!.should eq(Int{{n}}.new(1) << i)
(-1.to_big_i << i).to_i{{n}}!.should eq(Int{{n}}.new(-1) << i)
end

Int{{n}}::MAX.to_big_i.to_i{{n}}!.should eq(Int{{n}}::MAX)
Int{{n}}::MIN.to_big_i.to_i{{n}}!.should eq(Int{{n}}::MIN)
end

it "converts modulo (2 ** {{n}})" do
(1.to_big_i << {{n - 1}}).to_i{{n}}!.should eq(Int{{n}}::MIN)
(1.to_big_i << {{n}}).to_i{{n}}!.should eq(Int{{n}}.new(0))
(-1.to_big_i << {{n}}).to_i{{n}}!.should eq(Int{{n}}.new(0))
(123.to_big_i - (1.to_big_i << {{n}})).to_i{{n}}!.should eq(Int{{n}}.new(123))
(123.to_big_i + (1.to_big_i << {{n}})).to_i{{n}}!.should eq(Int{{n}}.new(123))
(123.to_big_i - (1.to_big_i << {{n + 2}})).to_i{{n}}!.should eq(Int{{n}}.new(123))
(123.to_big_i + (1.to_big_i << {{n + 2}})).to_i{{n}}!.should eq(Int{{n}}.new(123))
end
end
{% end %}

it "does String#to_big_i" do
"123456789123456789".to_big_i.should eq(BigInt.new("123456789123456789"))
Expand Down
152 changes: 93 additions & 59 deletions src/big/big_int.cr
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ struct BigInt < Int
num = num.abs_unsigned
capacity = (num.bit_length - 1) // (sizeof(LibGMP::MpLimb) * 8) + 1

# This assumes GMP wasn't built with its experimental nails support:
# https://gmplib.org/manual/Low_002dlevel-Functions
unsafe_build(capacity) do |limbs|
appender = limbs.to_unsafe.appender
limbs.size.times do
Expand All @@ -85,6 +87,16 @@ struct BigInt < Int
new(mpz)
end

# Returns a read-only `Slice` of the limbs that make up this integer, which
# is effectively `abs.digits(2 ** N)` where `N` is the number of bits in
# `LibGMP::MpLimb`, except that an empty `Slice` is returned for zero.
#
# This assumes GMP wasn't built with its experimental nails support:
# https://gmplib.org/manual/Low_002dlevel-Functions
private def limbs
Slice.new(LibGMP.limbs_read(self), LibGMP.size(self), read_only: true)
end

# :ditto:
#
# *num* must be finite.
Expand Down Expand Up @@ -621,92 +633,114 @@ struct BigInt < Int
to_i32
end

def to_i8 : Int8
to_i32.to_i8
def to_i! : Int32
to_i32!
end

def to_i16 : Int16
to_i32.to_i16
def to_u : UInt32
to_u32
end

def to_i32 : Int32
LibGMP.get_si(self).to_i32
def to_u! : UInt32
to_u32!
end

def to_i64 : Int64
if LibGMP::Long::MIN <= self <= LibGMP::Long::MAX
LibGMP.get_si(self).to_i64
else
to_s.to_i64 { raise OverflowError.new }
{% for n in [8, 16, 32, 64, 128] %}
def to_i{{n}} : Int{{n}}
\{% if Int{{n}} == LibGMP::SI %}
LibGMP.{{ flag?(:win32) ? "fits_si_p".id : "fits_slong_p".id }}(self) != 0 ? LibGMP.get_si(self) : raise OverflowError.new
\{% elsif Int{{n}}::MAX.is_a?(NumberLiteral) && Int{{n}}::MAX < LibGMP::SI::MAX %}
LibGMP::SI.new(self).to_i{{n}}
\{% else %}
to_primitive_i(Int{{n}})
\{% end %}
end
end

def to_i!
to_i32!
end

def to_i8! : Int8
LibGMP.get_si(self).to_i8!
end
def to_u{{n}} : UInt{{n}}
\{% if UInt{{n}} == LibGMP::UI %}
LibGMP.{{ flag?(:win32) ? "fits_ui_p".id : "fits_ulong_p".id }}(self) != 0 ? LibGMP.get_ui(self) : raise OverflowError.new
\{% elsif UInt{{n}}::MAX.is_a?(NumberLiteral) && UInt{{n}}::MAX < LibGMP::UI::MAX %}
LibGMP::UI.new(self).to_u{{n}}
\{% else %}
to_primitive_u(UInt{{n}})
\{% end %}
end

def to_i16! : Int16
LibGMP.get_si(self).to_i16!
end
def to_i{{n}}! : Int{{n}}
to_u{{n}}!.to_i{{n}}!
end

def to_i32! : Int32
LibGMP.get_si(self).to_i32!
end
def to_u{{n}}! : UInt{{n}}
\{% if UInt{{n}} == LibGMP::UI %}
LibGMP.get_ui(self) &* sign
\{% elsif UInt{{n}}::MAX.is_a?(NumberLiteral) && UInt{{n}}::MAX < LibGMP::UI::MAX %}
LibGMP::UI.new!(self).to_u{{n}}!
\{% else %}
to_primitive_u!(UInt{{n}})
\{% end %}
end
{% end %}

def to_i64! : Int64
(self % BITS64).to_u64.to_i64!
private def to_primitive_i(type : T.class) : T forall T
self >= 0 ? to_primitive_i_positive(T) : to_primitive_i_negative(T)
end

def to_u : UInt32
to_u32
private def to_primitive_u(type : T.class) : T forall T
self >= 0 ? to_primitive_i_positive(T) : raise OverflowError.new
end

def to_u8 : UInt8
to_u32.to_u8
end
private def to_primitive_u!(type : T.class) : T forall T
limbs = self.limbs
max_bits = sizeof(T) * 8
bits_per_limb = sizeof(LibGMP::MpLimb) * 8

def to_u16 : UInt16
to_u32.to_u16
x = T.zero
limbs.each_with_index do |limb, i|
break if i * bits_per_limb >= max_bits
x |= T.new!(limb) << (i * bits_per_limb)
end
x &* sign
end

def to_u32 : UInt32
to_u64.to_u32
end
private def to_primitive_i_positive(type : T.class) : T forall T
limbs = self.limbs
bits_per_limb = sizeof(LibGMP::MpLimb) * 8

def to_u64 : UInt64
if LibGMP::ULong::MIN <= self <= LibGMP::ULong::MAX
LibGMP.get_ui(self).to_u64
else
to_s.to_u64 { raise OverflowError.new }
highest_limb_index = (sizeof(T) * 8 - 1) // bits_per_limb
raise OverflowError.new if limbs.size > highest_limb_index + 1
if highest_limb = limbs[highest_limb_index]?
mask = LibGMP::MpLimb.new!(T::MAX >> (bits_per_limb * highest_limb_index))
raise OverflowError.new if highest_limb > mask
end
end

def to_u!
to_u32!
end

def to_u8! : UInt8
LibGMP.get_ui(self).to_u8!
x = T.zero
preshift_limit = T::MAX >> bits_per_limb
limbs.reverse_each do |limb|
x <<= bits_per_limb
x |= limb
end
x
end

def to_u16! : UInt16
LibGMP.get_ui(self).to_u16!
end
private def to_primitive_i_negative(type : T.class) : T forall T
limbs = self.limbs
bits_per_limb = sizeof(LibGMP::MpLimb) * 8

def to_u32! : UInt32
LibGMP.get_ui(self).to_u32!
end
x = T.zero.abs_unsigned
limit = T::MIN.abs_unsigned
preshift_limit = limit >> bits_per_limb
limbs.reverse_each do |limb|
raise OverflowError.new if x > preshift_limit
x <<= bits_per_limb

def to_u64! : UInt64
(self % BITS64).to_u64
# precondition: T must be larger than LibGMP::MpLimb, otherwise overflows
# like `0_i8 | 256` would happen and `x += limb` should be called instead
x |= limb
raise OverflowError.new if x > limit
end
x.neg_signed
end

private BITS64 = BigInt.new(1) << 64

def to_f : Float64
to_f64
end
Expand Down
Loading