From 3a7b9ca93b91dcc086b9ac8b9957e59268f9493b Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Sun, 6 Apr 2025 18:36:06 +0900 Subject: [PATCH] Fix `Integer.sqrt` to never exceed actual value `Integer.sqrt` uses `sqrt(3)` from libm for small values. This method must return a value less than or equal to the actual integer square root, but libm's sqrt does not always guarantee that. This change corrects that by decrementing the result if necessary. Fixes [Bug #21217] --- numeric.c | 6 +++++- test/ruby/test_integer.rb | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/numeric.c b/numeric.c index dcf4cf5c04..d3affed804 100644 --- a/numeric.c +++ b/numeric.c @@ -5978,7 +5978,11 @@ prefix##_isqrt(argtype n) \ while ((t = n/x) < (argtype)x) x = (rettype)((x + t) >> 1); \ return x; \ } \ - return (rettype)sqrt(argtype##_TO_DOUBLE(n)); \ + rettype x = (rettype)sqrt(argtype##_TO_DOUBLE(n)); \ + /* libm sqrt may returns a larger approximation than actual. */ \ + /* Our isqrt always returns a smaller approximation. */ \ + if (x * x > n) x--; \ + return x; \ } #if SIZEOF_LONG*CHAR_BIT > DBL_MANT_DIG diff --git a/test/ruby/test_integer.rb b/test/ruby/test_integer.rb index 1dbb3fbb45..fb7aabba35 100644 --- a/test/ruby/test_integer.rb +++ b/test/ruby/test_integer.rb @@ -708,6 +708,10 @@ class TestInteger < Test::Unit::TestCase assert_equal(x, Integer.sqrt(x ** 2), "[ruby-core:95453]") end + def test_bug_21217 + assert_equal(0x10000 * 2**10, Integer.sqrt(0x100000008 * 2**20)) + end + def test_fdiv assert_equal(1.0, 1.fdiv(1)) assert_equal(0.5, 1.fdiv(2))