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

Additions to Big arithmetics #4653

Merged
merged 19 commits into from
Sep 1, 2017
Merged

Conversation

akzhan
Copy link
Contributor

@akzhan akzhan commented Jul 1, 2017

  • Math.frexp(BigFloat) added.
  • BigRational.to_big_f and BigInt.to_big_f added.
  • BigFloat.trunc added with specs that shows difference with floor.
  • More number converters and constructors.
  • BigFloat./(Int32/Int64) specialization.
  • BigInt.divmod specialization.

Follows #4560.

This pull request includes changes required to support number normalization for #4675.

@akzhan akzhan changed the title Math.frexp(value : BigFloat) has native implementation. Math.frexp(value : BigFloat | BigInt) has native implementation. Jul 2, 2017
module Math
def frexp(value : BigFloat)
frac = LibGMP.mpf_get_d_2exp(out exp, value)
{frac, exp}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementation for exact calculation (for example, for exact hashing) should look like

module Math
  def frexp(value : BigFloat)
    LibGMP.mpf_get_d_2exp(out exp, value)
    frac = BigFloat.new { |mpf|
      if exp >= 0      
        LibGMP.mpf_div_2exp(mpf, self, exp)
      else
        LibGMP.mpf_mul_2exp(mpf, self, -exp)
      end
    }
    {frac, exp}
  end
end

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, will do


module Math
def frexp(value : BigInt)
frac = LibGMP.get_d_2exp(out exp, value)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather fallback to frexp(BigFloat.new(value)).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just thinking about -)

@akzhan akzhan changed the title Math.frexp(value : BigFloat | BigInt) has native implementation. Additions to Big arithmetics Jul 7, 2017
@funny-falcon
Copy link
Contributor

I've made BigFloat.new(BigInt) and BigFloat.new(BigRational) as a part of #4675.

@funny-falcon
Copy link
Contributor

(but i forgot to add BigInt#to_big_f and BigRational#to_big_f)

I mean: there will be collisions between this PR and #4675.

@akzhan
Copy link
Contributor Author

akzhan commented Jul 7, 2017

It's ok. And to_big_f is crystal way.

@akzhan
Copy link
Contributor Author

akzhan commented Jul 7, 2017

May be we need to copy to_big_f to your PR, because it's usage looks more clear.

@funny-falcon
Copy link
Contributor

I found mpf_set_q doesn't do what most expected from.
I mean: BigRational.new(a,b).to_big_f != BigFloat.new(a)/BigFloat.new(b)
Test with a=10000000000000001;b=10000000000000000

@akzhan
Copy link
Contributor Author

akzhan commented Jul 7, 2017

require "big"

def tst(a, b)
 p BigRational.new(a,b).to_big_f
 p BigFloat.new(a) / BigFloat.new(b)
end

tst(10000000000000001, 10000000000000000)
➜  crystal git:(bigfloat-frexp) ✗  bin/crystal test.cr
Using compiled compiler at `.build/crystal'
1.0000000000000001
1

to_big_f works fine. But BigFloat./ doesn't have good precision.

@funny-falcon
Copy link
Contributor

Yes, it looks like my version of BigFloat.new(BigRational) has a mistake.
Can you look at, please?
Your BigRational#to_big_f is correct. I don't understand where is difference.

@funny-falcon
Copy link
Contributor

I see: compiler ignores my BigFloat.new(BigRational) and calls BigFloat.new(Number) :-(

@funny-falcon
Copy link
Contributor

Fixed : it was need to require "big/big_rational" for some reason.

Note: if you do BigFloat constructors for every type (like in my PR), then there will be no need to add #to_big_f for every type, cause Number#to_big_f should do the thing.

@akzhan
Copy link
Contributor Author

akzhan commented Jul 7, 2017

Thanks, @funny-falcon for clarification but this is strange. Anyway I'll try to copy GMP related logic here to simplify any merge etc.

@akzhan
Copy link
Contributor Author

akzhan commented Jul 7, 2017

Hmm, dropping of to_big_f for BigInt throws bug. Kinda keeping of to_big_f did behavior predictable.

  1) BigInt does to_big_f
     Failure/Error: a.to_big_f.should eq(BigFloat.new("1234567890123456789.0"))

       Expected: 1234567890123456789
            got: 1234567890123456768

     # spec/std/big/big_int_spec.cr:227

makenowjust added a commit to makenowjust/crystal that referenced this pull request Jul 13, 2017
The ASTNode of `1 <= 2 <= 3` is `to_s`-ed to `(1 <= 2) <= 3`.
Right side parenthesis is not required and it causes crystal-lang#4653 error.
@RX14
Copy link
Contributor

RX14 commented Jul 14, 2017

@akzhan build is failing because of formatter.

@akzhan
Copy link
Contributor Author

akzhan commented Jul 14, 2017

@RX14 Fixed, thanks.

@@ -1,5 +1,6 @@
require "c/string"
require "./big"
require "./big_rational" # Strange but required for working BigFloat.new(BigRational) on Travis CI.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like it didn't help :/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bad kind of magic was restored not exactly as @funny-falcon proposed. Just updated (сross fingers). Will create issue for this.

@akzhan
Copy link
Contributor Author

akzhan commented Jul 14, 2017

@funny-falcon this trick doesn't help :-(

@akzhan
Copy link
Contributor Author

akzhan commented Jul 14, 2017

@Sija @funny-falcon I'll drop constructor(BigRational) due to it's unpredictable usage behavior by current Crystal compiler.

Anyway we should prefer to_big_f.

@Sija
Copy link
Contributor

Sija commented Jul 14, 2017

shouldn't constructor(BigRational) be placed in big/big_rational.cr instead?

@akzhan
Copy link
Contributor Author

akzhan commented Jul 14, 2017

Ok, will try :)

end

def /(other : UInt8 | UInt16 | UInt32)
raise DivisionByZero.new if other == 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about just self / LibGMP::ULong.new(other)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's ok too.

@funny-falcon
Copy link
Contributor

Excuse me, I was wrong in previous comment.

@akzhan akzhan changed the title Additions to Big arithmetics [WIP] Additions to Big arithmetics Jul 16, 2017
@akzhan
Copy link
Contributor Author

akzhan commented Aug 28, 2017

@RX14 looks like ready to approve

@funny-falcon
Copy link
Contributor

funny-falcon commented Aug 28, 2017

@funny-falcon Agrrhh, I can't reproduce bug that found by #4653 (comment)

@akzhan here is how it could be reproduced on master (and my branch) quite easy:
here is example when exception should be thrown, but it were not:

yura@falcon-work:~/Projects/crystal$ git diff           
diff --git a/src/big/big_float.cr b/src/big/big_float.cr
index d747e0c73..361fbec7c 100644                       
--- a/src/big/big_float.cr                              
+++ b/src/big/big_float.cr                              
@@ -17,6 +17,12 @@ struct BigFloat < Float              
     LibGMP.mpf_init_set_str(out @mpf, str, 10)         
   end                                                  
                                                        
+  def initialize(rat : BigRational)                    
+    raise "Rat"                                        
+    LibGMP.mpf_init(out @mpf)               
+    LibGMP.mpf_set_q(self, num)             
+  end                                                  
+                                                       
   def initialize(num : Number)                         
     LibGMP.mpf_init_set_d(out @mpf, num.to_f64)        
   end                                                  

$ bin/crystal eval 'require "big"; p BigFloat.new(BigRational.new(1,1))'
Using compiled compiler at `.build/crystal'                                                                
1_big_f                                                                                                    

@akzhan
Copy link
Contributor Author

akzhan commented Aug 28, 2017

@RX14 thanks to @funny-falcon - now broken overload issue has been extracted to #4897

@@ -18,7 +18,37 @@ struct BigFloat < Float
end

def initialize(num : Number)
LibGMP.mpf_init_set_d(out @mpf, num.to_f64)
case num
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this new arrangement where there is no Number overload trigger the bug? If no we should use overloads here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is workaround.
Here was comment "XXX fix me when overload will work", but Akzhan removed it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now will be extracted as issue and link added

Copy link
Contributor

@RX14 RX14 Aug 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But does this specific code still break the specs. If it does not (and I don't believe it does, as you removed the Int overload) the workaround should be removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RX14 this code once breaks the specs in some ways, use case has been extracted to #4897.

We must do this way until #4897 be fixed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just tested, extracted overloads works ok.

But it doesn't OK because I don't know why it sometimes didn't work - as exposed by #4897.

Compiler has unpredictable behavior to choose which overload will be chosen :-/

Copy link
Contributor Author

@akzhan akzhan Aug 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RX14 - Moreover, added overloads break specs at all. Again.

  1) BigFloat new new(BigInt)
     Failure/Error: bigfloat_on_bigint_value.should eq(bigfloat_of_integer_value)
       Expected: 123456789012345678901_big_f
            got: 123456789012345667584_big_f
     # spec/std/big/big_float_spec.cr:18
  2) BigFloat new new(BigRational)
     Failure/Error: bigfloat_on_bigrational_value.should eq(BigFloat.new(1) / BigFloat.new(3))
       Expected: 0.333333333333333333333_big_f
            got: 0.33333333333333331483_big_f

Copy link
Contributor Author

@akzhan akzhan Aug 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hate Crystal compiler overloads for now. They have unpredictable behavior.

Copy link
Contributor

@RX14 RX14 Aug 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bug is very clearly related to using Number or Int overloads (both of which are superclasses of BigInt). Simply avoid such generic overloads. Your case statement didn't have them, you don't need them.

There is no need to be "scared" of a bug, there is almost 0 chance that the bug will depend on anything but the list of overloads and their order. The bug seems fairly predictable to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have initialize(num : Number) anyway

@akzhan
Copy link
Contributor Author

akzhan commented Aug 29, 2017

@RX14 What can I do with these compiler bugs after returning of overloads? I want to rollback proposed overloads due to its erroneous.

by the way - bin/crystal spec/std/big/big_float_spec.cr works ok (failed only std spec)

Previous solution works fine in any conditions. No Crystal Voodoo overloads magic.

@funny-falcon
Copy link
Contributor

@RX14 Travis-CI failed with constructor overloads. Until compiler fixed, code should be reverted to 'case' overload.

LibGMP.mpf_set_z(self, num.to_big_i)
end
end

def initialize(num : Number)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I suspected, removing this type restriction fixes the issue.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, def initialize(num) works.
But it is ridiculous.
Should this method accept anything with #to_f64 method, or it should accept only Number?
If latter, then this restriction could not be removed.

Copy link
Contributor

@RX14 RX14 Aug 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather merge this with the overloads working and not have to change it later than merge it with the ugly case + comment in initialize. We can reproduce the failure so we can investigate it in parallel.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just fix overloads by forward declaration of types as @asterite points to.

Copy link
Contributor

@RX14 RX14 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finally!

@akzhan
Copy link
Contributor Author

akzhan commented Aug 31, 2017

Good time to merge :-)

@RX14
Copy link
Contributor

RX14 commented Aug 31, 2017

Need another review on this before it's merged.

@RX14 RX14 added this to the Next milestone Sep 1, 2017
@RX14 RX14 merged commit d1960a5 into crystal-lang:master Sep 1, 2017
@akzhan akzhan deleted the bigfloat-frexp branch September 1, 2017 14:17
funny-falcon added a commit to funny-falcon/crystal that referenced this pull request Sep 3, 2017
Prepare hash infrastructor to future change of hashing algrorithm
to protect against Hash DoS.
Class|Struct should define method `def hash(hasher)` and call
`hasher << @ivar` inside.

As an option, for speed, and for backward compatibility, `def hash`
still could be implemented. It will be used for Hash of matched type.
`Thread#hash` and `Signal#hash` is implemented as unseeded cause they are
 used before `StdHasher @@seed` is initialized.

Hash::Hasher is default hasher that uses `hash(hasher)` and it is used as
default seeded hasher.

Also, number normalization for hashing introduced, ie rule 'equality
forces hash equality' is forced (`a == b` => `a.hash == b.hash`).
Normalization idea is borrowed from Python implementation.
It fixes several issues with BigInt and BigFloat on 32bit platform,
but not all issues.

Fixes crystal-lang#4578
Fixes crystal-lang#3932
Prerequisite for crystal-lang#4557
Replaces crystal-lang#4581
Correlates with crystal-lang#4653
funny-falcon added a commit to funny-falcon/crystal that referenced this pull request Sep 3, 2017
Prepare hash infrastructor to future change of hashing algrorithm
to protect against Hash DoS.
Class|Struct should define method `def hash(hasher)` and call
`hasher << @ivar` inside.

As an option, for speed, and for backward compatibility, `def hash`
still could be implemented. It will be used for Hash of matched type.
`Thread#hash` and `Signal#hash` is implemented as unseeded cause they are
 used before `StdHasher @@seed` is initialized.

Hash::Hasher is default hasher that uses `hash(hasher)` and it is used as
default seeded hasher.

Also, number normalization for hashing introduced, ie rule 'equality
forces hash equality' is forced (`a == b` => `a.hash == b.hash`).
Normalization idea is borrowed from Python implementation.
It fixes several issues with BigInt and BigFloat on 32bit platform,
but not all issues.

Fixes crystal-lang#4578
Fixes crystal-lang#3932
Prerequisite for crystal-lang#4557
Replaces crystal-lang#4581
Correlates with crystal-lang#4653
funny-falcon added a commit to funny-falcon/crystal that referenced this pull request Sep 3, 2017
Prepare hash infrastructor to future change of hashing algrorithm
to protect against Hash DoS.
Class|Struct should define method `def hash(hasher)` and call
`hasher << @ivar` inside.

As an option, for speed, and for backward compatibility, `def hash`
still could be implemented. It will be used for Hash of matched type.
`Thread#hash` and `Signal#hash` is implemented as unseeded cause they are
 used before `StdHasher @@seed` is initialized.

Hash::Hasher is default hasher that uses `hash(hasher)` and it is used as
default seeded hasher.

Also, number normalization for hashing introduced, ie rule 'equality
forces hash equality' is forced (`a == b` => `a.hash == b.hash`).
Normalization idea is borrowed from Python implementation.
It fixes several issues with BigInt and BigFloat on 32bit platform,
but not all issues.

Fixes crystal-lang#4578
Fixes crystal-lang#3932
Prerequisite for crystal-lang#4557
Replaces crystal-lang#4581
Correlates with crystal-lang#4653
funny-falcon added a commit to funny-falcon/crystal that referenced this pull request Sep 3, 2017
Prepare hash infrastructor to future change of hashing algrorithm
to protect against Hash DoS.
Class|Struct should define method `def hash(hasher)` and call
`hasher << @ivar` inside.

As an option, for speed, and for backward compatibility, `def hash`
still could be implemented. It will be used for Hash of matched type.
`Thread#hash` and `Signal#hash` is implemented as unseeded cause they are
 used before `StdHasher @@seed` is initialized.

Hash::Hasher is default hasher that uses `hash(hasher)` and it is used as
default seeded hasher.

Also, number normalization for hashing introduced, ie rule 'equality
forces hash equality' is forced (`a == b` => `a.hash == b.hash`).
Normalization idea is borrowed from Python implementation.
It fixes several issues with BigInt and BigFloat on 32bit platform,
but not all issues.

Fixes crystal-lang#4578
Fixes crystal-lang#3932
Prerequisite for crystal-lang#4557
Replaces crystal-lang#4581
Correlates with crystal-lang#4653
@akzhan akzhan mentioned this pull request Sep 3, 2017
funny-falcon added a commit to funny-falcon/crystal that referenced this pull request Sep 10, 2017
Prepare hash infrastructor to future change of hashing algrorithm
to protect against Hash DoS.
Class|Struct should define method `def hash(hasher)` and call
`hasher << @ivar` inside.

As an option, for speed, and for backward compatibility, `def hash`
still could be implemented. It will be used for Hash of matched type.
`Thread#hash` and `Signal#hash` is implemented as unseeded cause they are
 used before `StdHasher @@seed` is initialized.

Hash::Hasher is default hasher that uses `hash(hasher)` and it is used as
default seeded hasher.

Also, number normalization for hashing introduced, ie rule 'equality
forces hash equality' is forced (`a == b` => `a.hash == b.hash`).
Normalization idea is borrowed from Python implementation.
It fixes several issues with BigInt and BigFloat on 32bit platform,
but not all issues.

Fixes crystal-lang#4578
Fixes crystal-lang#3932
Prerequisite for crystal-lang#4557
Replaces crystal-lang#4581
Correlates with crystal-lang#4653
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants