Bug in BigDecimal#round ?

R

Ryan Platte

A coworker discovered some disturbing behavior in BigDecimal#round.
The output I get from the program below is:

===== begin output

These values appended to '0.0000' cause BigDecimal#round to return
nonzero:
[5, 6, 7, 8, 9, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62,
63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96,
97, 98, 99]

These numbers of zeroes between the decimal point and a 7 cause
BigDecimal#round to return nonzero:
[4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72,
76, 80, 84, 88, 92, 96, 100]

===== end output

===== begin code

require 'bigdecimal'

vary_number_after_four_zeroes = (1..100).map { |i|
bd = BigDecimal.new("0.0000#{i}"); [i, bd.round.to_s]
}
seed_values_triggering_misbehavior_with_four_zeroes =
vary_number_after_four_zeroes.reject {|i, bds| bds == '0.0'}.map {|
i, bds| i}

vary_number_of_zeroes = (1..100).map { |i|
bd = BigDecimal.new("0.#{'0' * i}7"); [i, bd.round.to_s]
}
number_of_zeroes_triggering_misbehavior =
vary_number_of_zeroes.reject {|i, bds| bds == '0.0'}.map {|i, bds|
i}

puts "These values appended to '0.0000' cause BigDecimal#round to
return nonzero:"
p seed_values_triggering_misbehavior_with_four_zeroes
puts
puts "These numbers of zeroes between the decimal point and a 7 cause
BigDecimal#round to return nonzero:"
p number_of_zeroes_triggering_misbehavior

===== end code

I get identical reports on Linux and Windows versions of Ruby:

ruby 1.8.6 (2007-03-13 patchlevel 0) [i686-linux]

ruby 1.8.6 (2007-03-13 patchlevel 0) [i386-mswin32]

We would be grateful for a pointer to a patch that fixes this issue if
one is available.
 
J

Jano Svitok

A coworker discovered some disturbing behavior in BigDecimal#round.
The output I get from the program below is:

===== begin output

These values appended to '0.0000' cause BigDecimal#round to return
nonzero:
[5, 6, 7, 8, 9, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62,
63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96,
97, 98, 99]

These numbers of zeroes between the decimal point and a 7 cause
BigDecimal#round to return nonzero:
[4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72,
76, 80, 84, 88, 92, 96, 100]

===== end output

===== begin code

require 'bigdecimal'

vary_number_after_four_zeroes = (1..100).map { |i|
bd = BigDecimal.new("0.0000#{i}"); [i, bd.round.to_s]
}
seed_values_triggering_misbehavior_with_four_zeroes =
vary_number_after_four_zeroes.reject {|i, bds| bds == '0.0'}.map {|
i, bds| i}

vary_number_of_zeroes = (1..100).map { |i|
bd = BigDecimal.new("0.#{'0' * i}7"); [i, bd.round.to_s]
}
number_of_zeroes_triggering_misbehavior =
vary_number_of_zeroes.reject {|i, bds| bds == '0.0'}.map {|i, bds|
i}

puts "These values appended to '0.0000' cause BigDecimal#round to
return nonzero:"
p seed_values_triggering_misbehavior_with_four_zeroes
puts
puts "These numbers of zeroes between the decimal point and a 7 cause
BigDecimal#round to return nonzero:"
p number_of_zeroes_triggering_misbehavior

===== end code

I get identical reports on Linux and Windows versions of Ruby:

ruby 1.8.6 (2007-03-13 patchlevel 0) [i686-linux]

ruby 1.8.6 (2007-03-13 patchlevel 0) [i386-mswin32]

We would be grateful for a pointer to a patch that fixes this issue if
one is available.

It seems to me as a bug. The code in trunk is the same, so I guess
it's not fixed yet.
Please file a bug at
http://rubyforge.org/tracker/?func=add&group_id=426&atid=1698
(rubyforge.org, project ruby)

The problem is most probably in ext/bigdecimal/bigdecimal.c, VpMidRound().

As you have probably noticed, it happens when 1. the number of leading
zeros is divisible by 4 (BASE_FIG, the number of decimal digits stored
in one U_LONG), and the next digit is >= 5 (I guess due to selected
rounding mode)

That means, the problematic numbers have format 0.XE-Y where X in
[5..9] and Y%4 == 0

It's even visible in
BigDecimal.new("0.5").round(-4).to_i # => 10000
BigDecimal.new("0.000000005").round(4) #=> 0.1E-3 == 0.0001
BigDecimal.new("0.000000005").round(-4) #=> 0.1E5 == 10000
 
J

Jano Svitok

A coworker discovered some disturbing behavior in BigDecimal#round.
The output I get from the program below is:

===== begin output

These values appended to '0.0000' cause BigDecimal#round to return
nonzero:
[5, 6, 7, 8, 9, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62,
63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96,
97, 98, 99]

These numbers of zeroes between the decimal point and a 7 cause
BigDecimal#round to return nonzero:
[4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72,
76, 80, 84, 88, 92, 96, 100]

===== end output

===== begin code

require 'bigdecimal'

vary_number_after_four_zeroes = (1..100).map { |i|
bd = BigDecimal.new("0.0000#{i}"); [i, bd.round.to_s]
}
seed_values_triggering_misbehavior_with_four_zeroes =
vary_number_after_four_zeroes.reject {|i, bds| bds == '0.0'}.map {|
i, bds| i}

vary_number_of_zeroes = (1..100).map { |i|
bd = BigDecimal.new("0.#{'0' * i}7"); [i, bd.round.to_s]
}
number_of_zeroes_triggering_misbehavior =
vary_number_of_zeroes.reject {|i, bds| bds == '0.0'}.map {|i, bds|
i}

puts "These values appended to '0.0000' cause BigDecimal#round to
return nonzero:"
p seed_values_triggering_misbehavior_with_four_zeroes
puts
puts "These numbers of zeroes between the decimal point and a 7 cause
BigDecimal#round to return nonzero:"
p number_of_zeroes_triggering_misbehavior

===== end code

I get identical reports on Linux and Windows versions of Ruby:

ruby 1.8.6 (2007-03-13 patchlevel 0) [i686-linux]

ruby 1.8.6 (2007-03-13 patchlevel 0) [i386-mswin32]

We would be grateful for a pointer to a patch that fixes this issue if
one is available.

It seems to me as a bug. The code in trunk is the same, so I guess
it's not fixed yet.
Please file a bug at
http://rubyforge.org/tracker/?func=add&group_id=426&atid=1698
(rubyforge.org, project ruby)

The problem is most probably in ext/bigdecimal/bigdecimal.c, VpMidRound().

As you have probably noticed, it happens when 1. the number of leading
zeros is divisible by 4 (BASE_FIG, the number of decimal digits stored
in one U_LONG), and the next digit is >= 5 (I guess due to selected
rounding mode)

That means, the problematic numbers have format 0.XE-Y where X in
[5..9] and Y%4 == 0

It's even visible in
BigDecimal.new("0.5").round(-4).to_i # => 10000
BigDecimal.new("0.000000005").round(4) #=> 0.1E-3 == 0.0001
BigDecimal.new("0.000000005").round(-4) #=> 0.1E5 == 10000

reported as #14271
 
J

Jano Svitok

Thank you very much for reporting the bug.

I guess you can workaround the bug until it gets properly fixed - by
opening the BigDecimal class. Last night I could not come with
anything reasonable though ;-)

class BigDecimal
alias_method :eek:ld_round, :round
def round(*args)
if XXX
self.class.new(YYY)
else
old_round(*args)
end
end
end

the problematic parts are XXX and YYY... ;-)
args[0] and self.exponent might be usable...

I haven't found any unit tests for BigDecimal, so if you create some,
please post them somewhere (I'm not sure where - possible choises are:
ruby tracker, http://rubyforge.org/projects/bfts/ tracker, ruby-core
mailing list)

Jano
 
W

Wilson Bilkovich

Thank you very much for reporting the bug.

I guess you can workaround the bug until it gets properly fixed - by
opening the BigDecimal class. Last night I could not come with
anything reasonable though ;-)

class BigDecimal
alias_method :eek:ld_round, :round
def round(*args)
if XXX
self.class.new(YYY)
else
old_round(*args)
end
end
end

the problematic parts are XXX and YYY... ;-)
args[0] and self.exponent might be usable...

I haven't found any unit tests for BigDecimal, so if you create some,
please post them somewhere (I'm not sure where - possible choises are:
ruby tracker, http://rubyforge.org/projects/bfts/ tracker, ruby-core
mailing list)

JRuby has some:
http://svn.codehaus.org/jruby/trunk/jruby/test/test_big_decimal.rb

If you come up with some more, you could either submit them as a patch
there, or post them to ruby-core.

When we get around to BigDecimal in Rubinius, we will probably use the
JRuby test case as a starting point, and then fill in any gaps we
find. If you've posted some of them to ruby-core, that makes life
easier.
 
S

Shigeo Kobayashi

------=_NextPart_000_00BB_01C80AB7.DE386570
Content-Type: text/plain;
format=flowed;
charset="ISO-8859-1";
reply-type=response
Content-Transfer-Encoding: 7bit

Subject: Re: Bug in BigDecimal#round ?

I attached the fix as a patch file.
Could anyone(perhaps Matz ?) apply the patch ?

With a change log like:
Round method bug pointed by Ryan Platte fixed(Patch of the patch from
"NATORI Shin").

Thank you in advance.

Shigeo Kobayashi.

------=_NextPart_000_00BB_01C80AB7.DE386570
Content-Type: application/octet-stream;
name="bigdecimal_round.patch"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
filename="bigdecimal_round.patch"

--- bigdecimal.c.old 2007-10-02 20:21:52.421875000 +0900=0A=
+++ bigdecimal.c 2007-10-04 20:22:07.234375000 +0900=0A=
@@ -4319,7 +4319,6 @@=0A=
=0A=
/*=0A=
*=0A=
- * f =3D 0: Round off/Truncate, 1: round up, 2:ceil, 3: floor, 4: =
Banker's rounding=0A=
* nf: digit position for operation.=0A=
*=0A=
*/=0A=
@@ -4340,15 +4339,22 @@=0A=
nf +=3D y->exponent*((int)BASE_FIG);=0A=
exptoadd=3D0;=0A=
if (nf < 0) {=0A=
+ /* rounding position too left(large). */=0A=
+ if((f!=3DVP_ROUND_CEIL) && (f!=3DVP_ROUND_FLOOR)) {=0A=
+ VpSetZero(y,VpGetSign(y)); /* truncate everything */=0A=
+ return 0;=0A=
+ }=0A=
exptoadd =3D -nf;=0A=
nf =3D 0;=0A=
}=0A=
+=0A=
/* ix: x->fraq[ix] contains round position */=0A=
ix =3D nf/(int)BASE_FIG;=0A=
- if(((U_LONG)ix)>=3Dy->Prec) return 0; /* Unable to round */=0A=
+ if(((U_LONG)ix)>=3Dy->Prec) return 0; /* rounding position too =
right(small). */=0A=
ioffset =3D nf - ix*((int)BASE_FIG);=0A=
=0A=
v =3D y->frac[ix];=0A=
+=0A=
/* drop digits after pointed digit */=0A=
n =3D BASE_FIG - ioffset - 1;=0A=
for(shifter=3D1,i=3D0;i<n;++i) shifter *=3D 10;=0A=

------=_NextPart_000_00BB_01C80AB7.DE386570--
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top