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

Fix Float#<=> when other responds to infinite? #679

Merged

Conversation

stevegeek
Copy link
Contributor

Fixes cmp to support comparing infinity to rhs which responds to infinite?, and updates the spec to current upstream comparison spec.

A quick point for discussion:

here I use ->as_integer() on the Value returned from infinite? (which should return 1, nil, or -1) after checking for nil... however it is possible that an implementation of that method returns a non-integer value. If this is the case an assertion error will occur here.

If we look at Ruby, it handles this by calling rb_cmpint which effectively converts the value into an integer of the set of valid values [-1, 0, 1]. It does this for non-integer values by calling < 0 and > 0 on the object - https://github.com/ruby/ruby/blob/5ccb625fbbd1e774636a9fdbe0bf1c3d38e085d5/bignum.c#L2969

I could implement that same behaviour here in-line, eg I see this is done in-line here for example:

if (result->send(env, ">"_s, { Value::integer(0) })->is_true()) {

But I guess a function such as rb_cmpint would be better, to be used generally where return values for use with cmp need converting (eg in Time as linked above).

However I'm not sure where that function would live in natalie, any thoughts welcome!

@stevegeek
Copy link
Contributor Author

For example, these should pass:

  it "returns 1 when self is Infinity and other infinite? returns value that responds is neither > or < than 0" do
    obj = Object.new
    def obj.infinite?
      val = Object.new
      def val.<(other)
        false
      end
      def val.>(other)
        false
      end
      val
    end
    (infinity_value <=> obj).should == 1
  end

  it "returns -1 when self is -Infinity and other infinite?=1 via value that responds to > and is greater than 0" do
    obj = Object.new
    def obj.infinite?
      val = Object.new
      def val.>(other)
        true
      end
      val
    end
    (-infinity_value <=> obj).should == -1
  end

@seven1m
Copy link
Member

seven1m commented Oct 25, 2022

But I guess a function such as rb_cmpint would be better, to be used generally where return values for use with cmp need converting (eg in Time as linked above).
However I'm not sure where that function would live in natalie, any thoughts welcome!

We have the junk drawer that is natalie.hpp/cpp -- you can stick a function in there and that would be fine. But perhaps it would make sense to put it on the Object class, kind of like we do for to_s and to_str etc.

Fixes `cmp` to support comparing infinity to rhs which responds to `infinite?`, and updates the spec to current upstream comparison spec.
@stevegeek stevegeek force-pushed the fix_float_comparison_infinite branch from f4f6be7 to 540b100 Compare October 29, 2022 04:26
…-1,0,1) for use in <=>

Also adds method to raise 'comparison errors' which are ArgumentErrors with a common message format and way of handling the arguments. Can be used wherever a comparison error occurs
@stevegeek
Copy link
Contributor Author

@seven1m Do the latest changes look like they have gone in the right direction?

If so I could then refactor:

  1. places where to_cmp_int is needed and currently implemented inline (eg in Time)
  2. places where comparison errors are raised to instead use the method I added on Env (if its appropriate to add that method...)

src/object.cpp Outdated Show resolved Hide resolved
@seven1m
Copy link
Member

seven1m commented Nov 1, 2022

@stevegeek I had a minute to look at this today and decided to get a better handle on it myself. I made a commit to do pretty much the minimum to get the specs passing, and it looks good to me. I don't see the need for the to_cmp_int method right now, so I removed it. If you want to add your own tests to exercise it (we can add our own tests in the test/ directory), let's do it in another PR and add back the helper method you created!

Thanks so much for your work on this, I really appreciate it.

@seven1m seven1m marked this pull request as ready for review November 1, 2022 12:28
@stevegeek
Copy link
Contributor Author

Ok sure up to you! The point of to_cmp_int it to ensure a common and Ruby compatable way of handling the return values that are passed to <=>, in all cases where such a thing would happen, eg in Time comparison, Array sort etc, however understand your position!

Ill see if I can get a set of specs added upstream to ruby/spec that make the need for such a method clearer and then if necessary come back to it!

@seven1m
Copy link
Member

seven1m commented Nov 1, 2022

Yeah I think I see why we need the method, but we should have specs/tests driving that. Let's do it in another PR. I'm ok with us adding tests for it in the test/ directory.

@stevegeek
Copy link
Contributor Author

Ok sure!

@seven1m seven1m merged commit b67b13a into natalie-lang:master Nov 1, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants