Project

General

Profile

Actions

Bug #19231

open

Integer#step and Float::INFINITY - inconsistent behaviour when called with and without a block

Added by andrykonchin (Andrew Konchin) over 1 year ago. Updated 5 days ago.

Status:
Open
Assignee:
-
Target version:
-
[ruby-core:111272]

Description

The initial issue was reported here https://github.com/oracle/truffleruby/issues/2797.

0.step(Float::INFINITY, 10) returns:

  • Integers when called with a block
  • Floats when called without a block

I would expect Floats to be returned in both cases.

Examples:

0.step(Float::INFINITY, 10).take(1).map(&:class)
=> [Float]
0.step(Float::INFINITY, 10) { |offset| p offset.class; break }
# Integer

When to argument is a finite Float value then calling with a block returns Floats as well:

0.step(100.0, 10) { |offset| p offset.class; break }
# Float

Wondering whether it's intentional behaviour.

I've found a related issue https://bugs.ruby-lang.org/issues/15518.

Actions #1

Updated by andrykonchin (Andrew Konchin) over 1 year ago

  • Description updated (diff)

Updated by mrkn (Kenta Murata) over 1 year ago

0.step(Float::INFINITY, 10).each generates Float values until Ruby 2.0 and after Ruby 2.6, but it generates Integer values between Ruby 2.1 and 2.5. The reason why the behavior changes after Ruby 2.6 is due to ArithmeticSequence.

0.step(Float::INFINITY, 10) { ... } generates Float values until Ruby 2.0, but it generates Integer values after Ruby 2.1.

Hence it's also possible that the right behavior is to generate Integer values rather than Float. We need to know the reason for the behavior change in Ruby 2.1.

Updated by Eregon (Benoit Daloze) over 1 year ago

IMHO Integer makes sense so one can step by N from 0 to infinity with 0.step(Float::INFINITY, 10) (with Numeric#step), since there is no Integer::INFINITY.
Using Float also can cause significant errors with a big enough step or values.

So the rule would be "use floats if receiver or step is Float (ignore to/limit's type), otherwise leave them as-is.

The keyword form already uses integers for "step infinitely":

> 0.step(by: 10).take 2
=> [0, 10]
> 0.step(by: 10) { break _1 }
=> 0

Probably we need to be consistent with Range#step too:

> (0..).step(10) { break _1 }
=> 0
> (0..).step(10).take 2
=> [0, 10]
> (0..Float::INFINITY).step(10) { break _1 }
=> 0.0
> (0..Float::INFINITY).step(10).take 2
=> [0.0, 10.0]

Updated by kyanagi (Kouhei Yanagita) 5 months ago

% docker run -it --rm rubylang/all-ruby env ALL_RUBY_SINCE=ruby-1.3 ./all-ruby -e 'inf = 1.0/0.0; 0.step(inf, 10) { |x| p x; break }'
ruby-1.3              /tmp/rbFgfjB4:1:in `/': divided by 0 (ZeroDivisionError)
                        from /tmp/rbFgfjB4:1
                  exit 1
ruby-1.3.1-990215     0
...
ruby-1.6.8            0
ruby-1.8.0            0.0
...
ruby-2.0.0-p648       0.0
ruby-2.1.0-preview1   0
...
ruby-3.3.0-preview2   0

The behavior seems to have changed in version 1.8.0 due to https://github.com/ruby/ruby/commit/936ad40.

The behavior seems to have changed in version 2.1.0 due to https://github.com/ruby/ruby/commit/fd4b5b8.
The issue is https://bugs.ruby-lang.org/issues/8838.

I think the behavior change in Ruby 1.8.0 was likely not intentional but rather accidental.

I couldn't find any documentation indicating that the behavior change in Ruby 2.1.0 was intentional.
There is no mention in NEWS-2.1.0 either.

Updated by nobu (Nobuyoshi Nakada) 5 days ago

Weird a little.

seq = 0.step(Float::INFINITY, 10)
p seq.class     #=> Enumerator::ArithmeticSequence
p seq.first     #=> 0
p seq.first(1)  #=> [0.0]
Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0