From: Marc-Andre Lafortune Date: 2011-08-27T04:40:16+09:00 Subject: [ruby-core:39126] Re: [Ruby 1.9 - Bug #5227][Assigned] Float#round fails on corner cases Hi On Thu, Aug 25, 2011 at 9:46 PM, Yui NARUSE wrote: > r33061 tried to fix this but the result isn't changed. Sorry, I do not understand. r33061 fixes Integer#round, which had a completely different issue from Float#round (see redmine #5228) > Additional to say: > * add test for this to test/ruby/test_float.rb I already committed tests in RubySpec, which I believe is where they are the most useful. > * write Ticket number in ChangeLog and commit message > �(this is current limitation of Redmine-commit association) Ah, thanks, I didn't realize this had changed. I updated the contributer guidelines wiki page ( http://redmine.ruby-lang.org/projects/ruby/wiki/CommitterHowto ) but I don't have write access to another that should be updated accordingly: http://redmine.ruby-lang.org/projects/redmine/wiki/VersionControlSystem Can someone either give me access or update it? Thanks > ---------------------------------------- > Bug #5227: Float#round fails on corner cases > http://redmine.ruby-lang.org/issues/5227 > > Author: Marc-Andre Lafortune > Status: Assigned > Priority: Normal > Assignee: Marc-Andre Lafortune > Category: core > Target version: 1.9.3 > ruby -v: r32601 > > > Float#round fails on some corner cases: > > �42.0.round(300) # => 42.0 > �42.0.round(308) # => Infinity, should be 42.0 > �42.0.round(309) # => 42.0 > > �1.0e307.round(1) # => 1.0e307 > �1.0e307.round(2) # => Infinity, should be 1.0e307 > > These occur when the exponent of the intermediate value overflows. > > The original code already had criteria for extreme values, but we can find much tighter ones, as explained in the patch below. This fixes the bugs above and optimizes for most trivial cases. > > I'd be grateful if someone could look it over before I commit it, thanks. > > > diff --git a/numeric.c b/numeric.c > index 272bbd1..22608c9 100644 > --- a/numeric.c > +++ b/numeric.c > @@ -1491,18 +1491,37 @@ flo_round(int argc, VALUE *argv, VALUE num) > � � VALUE nd; > � � double number, f; > � � int ndigits = 0; > + � �int binexp; > � � long val; > > � � if (argc > 0 && rb_scan_args(argc, argv, "01", &nd) == 1) { > � � � �ndigits = NUM2INT(nd); > � � } > � � number �= RFLOAT_VALUE(num); > - � �f = pow(10, abs(ndigits)); > - > - � �if (isinf(f)) { > - � � � if (ndigits < 0) number = 0; > - � �} > - � �else { > + � �frexp (number , &binexp); > + > +/* Let `exp` be such that `number` is written as: "0.#{digits}e#{exp}", > + � i.e. such that �10 ** (exp - 1) <= |number| < 10 ** exp > + � Recall that up to 17 digits can be needed to represent a double, > + � so if ndigits + exp >= 17, the intermediate value (number * 10 ** ndigits) > + � will be an integer and thus the result is the original number. > + � If ndigits + exp <= 0, the result is 0 or "1e#{exp}", so > + � if ndigits + exp < 0, the result is 0. > + � We have: > + � � � 2 ** (binexp-1) <= |number| < 2 ** binexp > + � � � 10 ** ((binexp-1)/log_2(10)) <= |number| < 10 ** (binexp/log_2(10)) > + � � � If binexp >= 0, and since log_2(10) = 3.322259: > + � � � � �10 ** (binexp/4 - 1) < |number| < 10 ** (binexp/3) > + � � � � �binexp/4 <= exp <= binexp/3 > + � � � If binexp <= 0, swap the /4 and the /3 > + � � � So if ndigits + binexp/(3 or 4) >= 17, the result is number > + � � � If ndigits + binexp/(4 or 3) < 0 the result is 0 > +*/ > + � �if ((long)ndigits * (4 - (binexp < 0)) + binexp < 0) { > + � � � number = 0; > + � �} > + � �else if ((long)(ndigits - 17) * (3 + (binexp < 0)) + binexp < 0) { > + � � � f = pow(10, abs(ndigits)); > � � � �if (ndigits < 0) { > � � � � � �double absnum = fabs(number); > � � � � � �if (absnum < f) return INT2FIX(0); > > > > -- > http://redmine.ruby-lang.org > >