From: "mame (Yusuke Endoh) via ruby-core" Date: 2025-10-28T09:03:48+00:00 Subject: [ruby-core:123559] [Ruby Bug#21650] Performance regression: Rational#floor(ndigits) extremely slow for huge ndigits in Ruby 3.4 (ok in 3.2) Issue #21650 has been updated by mame (Yusuke Endoh). Thank you for the report. As the warning indicates, prior to Ruby 3.4 (up to 3.3), attempting to generate a huge `Integer` would return `Float::INFINITY`. Ruby 3.4 removed this inaccurate truncation, which is why it now takes longer. (#20811) The calculation for `m.floor(n)` is computed as `(m * 10**n).floor / (10**n)`. In Ruby 3.3 and earlier, when `10**n` became too large, the calculation would effectively "give up" and just return `m` *incorrectly*. This wasn't just *fast*; it was producing an **incorrect** result. Please look at the following example: ```ruby # returns Rational(1, 10**(2**n)) def calc(n) d = 10 n.times { d *= d } Rational(1, d) end p calc(1) #=> (1/100) p calc(2) #=> (1/10000) p calc(3) #=> (1/1000000) p calc(10).floor(2**10 - 1) == 0 #=> true # correct p calc(20).floor(2**20 - 1) == 0 #=> true # correct p calc(30).floor(2**30 - 1) == 0 #=> false # incorrect in Ruby 3.2 ``` `Rational(1, 10**(2**n)).floor(2**n - 1)` should return 0 for any integer `n`. However, as you can see, Ruby 3.2 returns a non-zero value (evaluates to `false`) for the last line. Running the same code in Ruby 3.4 correctly outputs `true` for all cases. Now, while there might be room to improve the algorithm and speed up `Rational#floor`, I would like to confirm a few things first: 1. What is your use case for code like `n.floor(2**31)`? 2. Given that the calculation result was incorrect in Ruby 3.3 and earlier, were you not encountering issues with that? ---------------------------------------- Bug #21650: Performance regression: Rational#floor(ndigits) extremely slow for huge ndigits in Ruby 3.4 (ok in 3.2) https://bugs.ruby-lang.org/issues/21650#change-114940 * Author: koilanetroc (Oleg Tolmashov) * Status: Open * ruby -v: ruby 3.4.7 (2025-10-08 revision 7a5688e2a2) +PRISM [arm64-darwin24] * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- ## Summary `Rational#floor(ndigits)` with a very large positive ndigits takes tens of seconds in Ruby 3.4, while it returns essentially instantly in Ruby 3.2. Reproducible on macOS and Linux. Looks like a missing fast���path for rationals whose decimal expansion terminates. ## Steps to reproduce ```ruby require "benchmark" puts RUBY_DESCRIPTION t = Benchmark.realtime { (2 ** -3).floor(2 ** 31) } puts "elapsed: #{t.round(3)}s" ``` Also reproduces with the explicit rational form: ```ruby Benchmark.realtime { Rational(1, 8).floor(2 ** 31) } ``` ### Results on my machine Ruby 3.2.8: ``` ruby 3.2.8 (2025-03-26 revision 13f495dc2c) [arm64-darwin24] slow_math.rb:4: warning: in a**b, b may be too big elapsed: 0.0s ``` Ruby 3.4.7: ``` ruby 3.4.7 (2025-10-08 revision 7a5688e2a2) +PRISM [arm64-darwin24] elapsed: 39.214s ``` ## Actual behavior On Ruby 3.4.x this call takes ~tens of seconds (e.g., ~40s on my machine), consuming CPU. Same on macOS and Linux. ## Expected behavior The method should return quickly ������������������ ���� -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/