diff --git a/spec/std/big/big_rational_spec.cr b/spec/std/big/big_rational_spec.cr index 0dcc75c10b87..ec8ed3d0621b 100644 --- a/spec/std/big/big_rational_spec.cr +++ b/spec/std/big/big_rational_spec.cr @@ -466,12 +466,6 @@ describe BigRational do it { br(7, -3).integer?.should be_false } end - it "#hash" do - b = br(10, 3) - hash = b.hash - hash.should eq(b.to_f64.hash) - end - it "is a number" do br(10, 3).is_a?(Number).should be_true end diff --git a/spec/std/crystal/hasher_spec.cr b/spec/std/crystal/hasher_spec.cr index 099b49af7755..456f8ce844a1 100644 --- a/spec/std/crystal/hasher_spec.cr +++ b/spec/std/crystal/hasher_spec.cr @@ -305,13 +305,14 @@ describe "Crystal::Hasher" do Crystal::Hasher.reduce_num(Float64::MAX).should eq(0x1F00_FFFF_FFFF_FFFF_u64) end - pending "reduces BigInt" do + it "reduces BigInt" do Crystal::Hasher.reduce_num(0.to_big_i).should eq(0_u64) Crystal::Hasher.reduce_num(1.to_big_i).should eq(1_u64) Crystal::Hasher.reduce_num((-1).to_big_i).should eq(UInt64::MAX) (1..300).each do |i| Crystal::Hasher.reduce_num(2.to_big_i ** i).should eq(1_u64 << (i % 61)) + Crystal::Hasher.reduce_num(-(2.to_big_i ** i)).should eq(&-(1_u64 << (i % 61))) end end @@ -324,8 +325,88 @@ describe "Crystal::Hasher" do (1..300).each do |i| Crystal::Hasher.reduce_num(2.to_big_f ** i).should eq(1_u64 << (i % 61)) + Crystal::Hasher.reduce_num(-(2.to_big_f ** i)).should eq(&-(1_u64 << (i % 61))) Crystal::Hasher.reduce_num(0.5.to_big_f ** i).should eq(1_u64 << ((-i) % 61)) + Crystal::Hasher.reduce_num(-(0.5.to_big_f ** i)).should eq(&-(1_u64 << ((-i) % 61))) end end + + it "reduces BigDecimal" do + Crystal::Hasher.reduce_num(0.to_big_d).should eq(0_u64) + Crystal::Hasher.reduce_num(1.to_big_d).should eq(1_u64) + Crystal::Hasher.reduce_num((-1).to_big_d).should eq(UInt64::MAX) + + # small inverse powers of 10 + Crystal::Hasher.reduce_num(BigDecimal.new(1, 1)).should eq(0x1CCCCCCCCCCCCCCC_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 2)).should eq(0x0FAE147AE147AE14_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 3)).should eq(0x0E5E353F7CED9168_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 4)).should eq(0x14A305532617C1BD_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 5)).should eq(0x05438088509BF9C6_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 6)).should eq(0x06ED2674080F98FA_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 7)).should eq(0x1A4AEA3ECD9B28E5_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 8)).should eq(0x12A1176CAE291DB0_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 9)).should eq(0x01DCE8BE116A82F8_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 10)).should eq(0x1362E41301BDD9E5_u64) + + # a^(p-1) === 1 (mod p) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 0x1FFFFFFFFFFFFFFE_u64)).should eq(1_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 0x1FFFFFFFFFFFFFFD_u64)).should eq(10_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 0x1FFFFFFFFFFFFFFC_u64)).should eq(100_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 0x1FFFFFFFFFFFFFFB_u64)).should eq(1000_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 0x1FFFFFFFFFFFFFFA_u64)).should eq(10000_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 0x1FFFFFFFFFFFFFF9_u64)).should eq(100000_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 0x1FFFFFFFFFFFFFF8_u64)).should eq(1000000_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 0x1FFFFFFFFFFFFFF7_u64)).should eq(10000000_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 0x1FFFFFFFFFFFFFF6_u64)).should eq(100000000_u64) + Crystal::Hasher.reduce_num(BigDecimal.new(1, 0x1FFFFFFFFFFFFFF5_u64)).should eq(1000000000_u64) + + (1..300).each do |i| + Crystal::Hasher.reduce_num(2.to_big_d ** i).should eq(1_u64 << (i % 61)) + Crystal::Hasher.reduce_num(-(2.to_big_d ** i)).should eq(&-(1_u64 << (i % 61))) + Crystal::Hasher.reduce_num(0.5.to_big_d ** i).should eq(1_u64 << ((-i) % 61)) + Crystal::Hasher.reduce_num(-(0.5.to_big_d ** i)).should eq(&-(1_u64 << ((-i) % 61))) + end + end + + it "reduces BigRational" do + Crystal::Hasher.reduce_num(0.to_big_r).should eq(0_u64) + Crystal::Hasher.reduce_num(1.to_big_r).should eq(1_u64) + Crystal::Hasher.reduce_num((-1).to_big_r).should eq(UInt64::MAX) + + # inverses of small integers + Crystal::Hasher.reduce_num(BigRational.new(1, 2)).should eq(0x1000000000000000_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 3)).should eq(0x1555555555555555_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 4)).should eq(0x0800000000000000_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 5)).should eq(0x1999999999999999_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 6)).should eq(0x1AAAAAAAAAAAAAAA_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 7)).should eq(0x1B6DB6DB6DB6DB6D_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 8)).should eq(0x0400000000000000_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 9)).should eq(0x1C71C71C71C71C71_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 10)).should eq(0x1CCCCCCCCCCCCCCC_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 11)).should eq(0x1D1745D1745D1745_u64) + + Crystal::Hasher.reduce_num(BigRational.new(1, 0x1000000000000000_u64)).should eq(2_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 0x1555555555555555_u64)).should eq(3_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 0x0800000000000000_u64)).should eq(4_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 0x1999999999999999_u64)).should eq(5_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 0x1AAAAAAAAAAAAAAA_u64)).should eq(6_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 0x1B6DB6DB6DB6DB6D_u64)).should eq(7_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 0x0400000000000000_u64)).should eq(8_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 0x1C71C71C71C71C71_u64)).should eq(9_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 0x1CCCCCCCCCCCCCCC_u64)).should eq(10_u64) + Crystal::Hasher.reduce_num(BigRational.new(1, 0x1D1745D1745D1745_u64)).should eq(11_u64) + + (1..300).each do |i| + Crystal::Hasher.reduce_num(2.to_big_r ** i).should eq(1_u64 << (i % 61)) + Crystal::Hasher.reduce_num(-(2.to_big_r ** i)).should eq(&-(1_u64 << (i % 61))) + Crystal::Hasher.reduce_num(0.5.to_big_r ** i).should eq(1_u64 << ((-i) % 61)) + Crystal::Hasher.reduce_num(-(0.5.to_big_r ** i)).should eq(&-(1_u64 << ((-i) % 61))) + end + + Crystal::Hasher.reduce_num(BigRational.new(1, 0x1FFF_FFFF_FFFF_FFFF_u64)).should eq(Crystal::Hasher::HASH_INF_PLUS) + Crystal::Hasher.reduce_num(BigRational.new(-1, 0x1FFF_FFFF_FFFF_FFFF_u64)).should eq(Crystal::Hasher::HASH_INF_MINUS) + Crystal::Hasher.reduce_num(BigRational.new(2, 0x1FFF_FFFF_FFFF_FFFF_u64)).should eq(Crystal::Hasher::HASH_INF_PLUS) + Crystal::Hasher.reduce_num(BigRational.new(-2, 0x1FFF_FFFF_FFFF_FFFF_u64)).should eq(Crystal::Hasher::HASH_INF_MINUS) + end end end diff --git a/src/big/big_decimal.cr b/src/big/big_decimal.cr index ab42c64bf919..a2c95fac2fff 100644 --- a/src/big/big_decimal.cr +++ b/src/big/big_decimal.cr @@ -753,10 +753,6 @@ struct BigDecimal < Number self end - def hash(hasher) - hasher.string(to_s) - end - # Returns the *quotient* as absolutely negative if `self` and *other* have # different signs, otherwise returns the *quotient*. def normalize_quotient(other : BigDecimal, quotient : BigInt) : BigInt @@ -879,3 +875,22 @@ class String BigDecimal.new(self) end end + +# :nodoc: +struct Crystal::Hasher + def self.reduce_num(value : BigDecimal) + v = reduce_num(value.value.abs) + + # v = UInt64.mulmod(v, 10_u64.powmod(-scale, HASH_MODULUS), HASH_MODULUS) + # TODO: consider #7516 or similar + scale = value.scale + x = 0x1ccc_cccc_cccc_cccc_u64 # 10^-1 (mod HASH_MODULUS) + while scale > 0 + v = UInt64.mulmod(v, x, HASH_MODULUS) if scale.bits_set?(1) + scale = scale.unsafe_shr(1) + x = UInt64.mulmod(x, x, HASH_MODULUS) + end + + v &* value.sign + end +end diff --git a/src/big/big_float.cr b/src/big/big_float.cr index 2cf224d7484a..cadc91282fc1 100644 --- a/src/big/big_float.cr +++ b/src/big/big_float.cr @@ -77,9 +77,6 @@ struct BigFloat < Float new(mpf) end - # TODO: improve this - def_hash to_f64 - def self.default_precision LibGMP.mpf_get_default_prec end diff --git a/src/big/big_int.cr b/src/big/big_int.cr index 1e3c3430c542..49738cb8bfbc 100644 --- a/src/big/big_int.cr +++ b/src/big/big_int.cr @@ -488,9 +488,6 @@ struct BigInt < Int LibGMP.sizeinbase(self, 2).to_i end - # TODO: check hash equality for numbers >= 2**63 - def_hash to_i64! - def to_s(base : Int = 10, *, precision : Int = 1, upcase : Bool = false) : String raise ArgumentError.new("Invalid base #{base}") unless 2 <= base <= 36 || base == 62 raise ArgumentError.new("upcase must be false for base 62") if upcase && base == 62 @@ -985,18 +982,14 @@ end # :nodoc: struct Crystal::Hasher - private HASH_MODULUS_INT_P = BigInt.new((1_u64 << HASH_BITS) - 1) - private HASH_MODULUS_INT_N = -BigInt.new((1_u64 << HASH_BITS) - 1) + private HASH_MODULUS_INT_P = BigInt.new(HASH_MODULUS) def self.reduce_num(value : BigInt) - # it should calculate `remainder(HASH_MODULUS)` - if LibGMP::UI == UInt64 - v = LibGMP.tdiv_ui(value, HASH_MODULUS).to_i64 - value < 0 ? -v : v - elsif value >= HASH_MODULUS_INT_P || value <= HASH_MODULUS_INT_N - value.unsafe_truncated_mod(HASH_MODULUS_INT_P).to_i64 - else - value.to_i64 - end + {% if LibGMP::UI == UInt64 %} + v = LibGMP.tdiv_ui(value, HASH_MODULUS) + value < 0 ? &-v : v + {% else %} + value.remainder(HASH_MODULUS_INT_P).to_u64! + {% end %} end end diff --git a/src/big/big_rational.cr b/src/big/big_rational.cr index b14c8f4b2d4a..cb4731dc4e95 100644 --- a/src/big/big_rational.cr +++ b/src/big/big_rational.cr @@ -285,9 +285,6 @@ struct BigRational < Number BigRational.new { |mpq| LibGMP.mpq_abs(mpq, self) } end - # TODO: improve this - def_hash to_f64 - # Returns the `Float64` representing this rational. def to_f : Float64 to_f64 @@ -459,18 +456,13 @@ end # :nodoc: struct Crystal::Hasher - private HASH_MODULUS_RAT_P = BigRational.new((1_u64 << HASH_BITS) - 1) - private HASH_MODULUS_RAT_N = -BigRational.new((1_u64 << HASH_BITS) - 1) - def self.reduce_num(value : BigRational) - rem = value - if value >= HASH_MODULUS_RAT_P || value <= HASH_MODULUS_RAT_N - num = value.numerator - denom = value.denominator - div = num.tdiv(denom) - floor = div.tdiv(HASH_MODULUS) - rem -= floor * HASH_MODULUS + inverse = BigInt.new do |mpz| + if LibGMP.invert(mpz, value.denominator, HASH_MODULUS_INT_P) == 0 + # inverse doesn't exist, i.e. denominator is a multiple of HASH_MODULUS + return value >= 0 ? HASH_INF_PLUS : HASH_INF_MINUS + end end - rem.to_big_f.hash + UInt64.mulmod(reduce_num(value.numerator.abs), inverse.to_u64!, HASH_MODULUS) &* value.sign end end diff --git a/src/big/lib_gmp.cr b/src/big/lib_gmp.cr index 3cae0de64b77..00834598d9d2 100644 --- a/src/big/lib_gmp.cr +++ b/src/big/lib_gmp.cr @@ -141,6 +141,7 @@ lib LibGMP fun gcd_ui = __gmpz_gcd_ui(rop : MPZ*, op1 : MPZ*, op2 : UI) : UI fun lcm = __gmpz_lcm(rop : MPZ*, op1 : MPZ*, op2 : MPZ*) fun lcm_ui = __gmpz_lcm_ui(rop : MPZ*, op1 : MPZ*, op2 : UI) + fun invert = __gmpz_invert(rop : MPZ*, op1 : MPZ*, op2 : MPZ*) : Int fun remove = __gmpz_remove(rop : MPZ*, op : MPZ*, f : MPZ*) : BitcntT # # Miscellaneous Functions diff --git a/src/int.cr b/src/int.cr index 35ad8c05f642..65a54e4e2fde 100644 --- a/src/int.cr +++ b/src/int.cr @@ -193,6 +193,32 @@ struct Int {% end %} end + # :nodoc: + # + # Computes (x * y) % z, but without intermediate overflows. + # Precondition: `0 <= x < z && y >= 0` + def self.mulmod(x, y, z) + result = zero + while y > 0 + if y.bits_set?(1) + # result = (result + x) % z + if result >= z &- x + result &-= z &- x + else + result &+= x + end + end + # x = (x + x) % z + if x >= z &- x + x &-= z &- x + else + x = x.unsafe_shl(1) + end + y = y.unsafe_shr(1) + end + result + end + # Returns the result of shifting this number's bits *count* positions to the right. # Also known as arithmetic right shift. #