Exclusive float range, Range#step result in counterintuitive result

Hi everybody,

I find that:

if
range.exclude_end? == true
and
[begin_obj, end_obj, step].grep(Float) != []
and
[begin_obj, end_obj, step].any? {|f| f != f.to_i}
then

result will miss the last one

end

for example:

p (1…6.3).step.to_a # => [1.0, 2.0, 3.0, 4.0, 5.0], missing 6.0
p (1.1…6).step.to_a # => [1.1, 2.1, 3.1, 4.1], missing 5.1
p (1…6).step(1.1).to_a # => [1.0, 2.1, 3.2, 4.300000000000001],
missing 5.4

p (1.0…6.6).step(1.9).to_a # => [1.0, 2.9], missing 4.8
p (1.0…6.7).step(1.9).to_a # => [1.0, 2.9, 4.8], 6.7 == 4.8 + 1.9,
it’s ok

The results seem so counterintuitive, is it a bug? Maybe I should report
it?

Thank you!

Joey

and another one on Range#max, also exclusive range:

p (1…9.3).max # cannot exclude non Integer end value (TypeError)
p (1…9.3).max {|a,b| a <=> b} #=> 9

I think the result should be the same. Maybe the first one should
also return 9, not an error.

On Thu, Apr 14, 2011 at 2:42 PM, Joey Z. [email protected] wrote:

then
p (1.0…6.6).step(1.9).to_a # => [1.0, 2.9], missing 4.8
p (1.0…6.7).step(1.9).to_a # => [1.0, 2.9, 4.8], 6.7 == 4.8 + 1.9,
it’s ok

The results seem so counterintuitive, is it a bug? Maybe I should report
it?

Hmmm… Float and Range generally don’t mix very well. Considering
this the results seem at least consistent - if not expected:

irb(main):001:0> (1.0…6.6).step(1.9).to_a
=> [1.0, 2.9]
irb(main):002:0> (1.0…6.6).step(1.9).to_a
=> [1.0, 2.9, 4.8]

Line 2 cannot go beyond 4.8 because then it would pass 6.6, so the
last value which can really be returned is 4.8. The third dot removes
that value from the list.

It’s at least a tad weird. +0.5 for opening a bug.

Kind regards

robert

Joey Z. wrote in post #992758:

The results seem so counterintuitive, is it a bug? Maybe I should report
it?

According to the docs for range.c (2005):


Ranges can be constructed using objects of any type, as long as the
objects can be compared using their <=> operator and they support the
succ method to return the next object in sequence.

…and

f = 1.2
puts f.succ

–output:–
prog.rb:2:in <main>': undefined methodsucc’ for 1.2:Float
(NoMethodError)

However, step() may not call succ():

rng.step(n=1)
If the range contains numbers, n is added for each iteration

This looks like a bug to me.

(The problem exists as of 1.9.2-p180.)

In <range.c>, range_step' is the implementation of Range#step. It callsruby_float_step’, which is implemented in <numeric.c>, to handle float
stepping.

The call looks like this:

else if (ruby_float_step(b, e, step, EXCL(range))) {

/* done */
}

The problem is that it passes the exclude_end?' property of the range toruby_float_step’, in our case it is true. Inside the implementation
of `ruby_float_step’ is something like:

  n = floor(n + err);
  if (!excl) n++;
  for (i=0; i<n; i++) {
      rb_yield(DBL2NUM(i*unit+beg));
  }

And excl is the last parameter. Clearly if the end value of the range is
x, then the iteration only goes to floor(x)-1.

On the other hand -

p 1.step(6.3).to_a # => [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]

This works since `num_step’, again implemented in <numeric.c>,
explicitly passes a false to excl:

else if (!ruby_float_step(from, to, step, FALSE)) {
    ...

Hi,

In message “Re: Exclusive float range, Range#step result in
counterintuitive result”
on Fri, 15 Apr 2011 01:47:03 +0900, 7stud –
[email protected] writes:
|
|Joey Z. wrote in post #992758:
|>
|> The results seem so counterintuitive, is it a bug? Maybe I should report
|> it?
|
|By definition, floats cannot be used in ranges.

By definition, floats cannot be used in ranges, when you want to
iterate over it.

          matz.

Yukihiro M. wrote in post #992890:

By definition, floats cannot be used in ranges, when you want to
iterate over it.

          matz.

But it seems that floats can be used in a range which can be iterated.

int…float is OK, it can call methods from Enumerable module.

But int…float sometimes result in confusion.