Math::BigFloat oddities

  • Thread starter Peter J. Acklam
  • Start date
P

Peter J. Acklam

I am totally out of clues as to why the following happens.
Please, let me know...

First, subtracting an element from itself should give 0...

$ perl -MMath::BigFloat -wle \
'$x = Math::BigFloat->new(3.14) ; $x = $x - $x ; print $x'
0

$ perl -MMath::BigFloat -wle \
'$x = Math::BigFloat->new(3.14) ; $x -= $x ; print $x'
6.28
^^^^

What was that?

Secondly, dividing an element by itself should give 1...

$ perl -MMath::BigFloat -wle \
'$x = Math::BigFloat->new(3.14) ; $x = $x / $x ; print $x'
1 # OK; x / x = 1

$ perl -MMath::BigFloat -wle \
'$x = Math::BigFloat->new(3.14) ; $x /= $x ; print $x'
0.00000000000000000000000000000000000000000001
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

What was that?

Is there a bug in the operator overloading of Math::BigFloat
objects?

Peter
 
A

Anno Siegel

Peter J. Acklam said:
I am totally out of clues as to why the following happens.
Please, let me know...

First, subtracting an element from itself should give 0...

$ perl -MMath::BigFloat -wle \
'$x = Math::BigFloat->new(3.14) ; $x = $x - $x ; print $x'
0

$ perl -MMath::BigFloat -wle \
'$x = Math::BigFloat->new(3.14) ; $x -= $x ; print $x'
6.28
^^^^

What was that?

Secondly, dividing an element by itself should give 1...

$ perl -MMath::BigFloat -wle \
'$x = Math::BigFloat->new(3.14) ; $x = $x / $x ; print $x'
1 # OK; x / x = 1

$ perl -MMath::BigFloat -wle \
'$x = Math::BigFloat->new(3.14) ; $x /= $x ; print $x'
0.00000000000000000000000000000000000000000001
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

What was that?

Is there a bug in the operator overloading of Math::BigFloat
objects?

The "-=" bug is already in Math::BigInt from which Math::BigFloat inherits
most of its overloading. I'm not sure what exactly is going on, but the
code jumps through quite a few hoops when it comes to protecting the
operands of overloading from unwanted changes. I suppose something is
going wrong there. I guess a bug report is in order.

Anno
 
J

Joe Smith

Purl said:
syntax error at -e line 1, at EOF
Execution of -e aborted due to compilation errors.

You will note in the example given, the prompt is $ not C:\>.
Those examples work just fine with bash or tcsh on Linux.
Perhaps your shell is broken?
-Joe
 
P

Peter J. Acklam

Joe Smith said:
You will note in the example given, the prompt is $ not C:\>.
Those examples work just fine with bash or tcsh on Linux.
Perhaps your shell is broken?

Neither command.com nor cmd.exe understands that backslash means
a broken line, nor do they undestand quoted strings using single
quotation marks.

Peter
 
J

J. Romano

(e-mail address removed)-berlin.de (Anno Siegel) replied in message
The "-=" bug is already in Math::BigInt from which Math::BigFloat inherits
most of its overloading. I'm not sure what exactly is going on, but the
code jumps through quite a few hoops when it comes to protecting the
operands of overloading from unwanted changes. I suppose something is
going wrong there. I guess a bug report is in order.


Okay, I looked inside the code for Math::BigInt (which has the same
problem as Math::BigFloat) and here is what is happening:

Using the '-=' operator on a Math::BigInt object calls the bsub()
method, which subtracts two Math::BigInt objects by doing the
following:

1. It flips the sign of the second object (to negate it)
2. It adds the two objects together (with the badd() method)
3. It flips the sign of the second object again (to restore it)

Apparently the bsub() method assumes that the first and second object
are NOT the same. But since they are, when steps 1 and 3 get carried
out, both the first object AND the second object get their sign
flipped. To clarify, consider the following code:

use Math::BigInt; # line 1
my $x = new Math::BigInt(3); # line 2
$x -= $x; # line 3 (which SHOULD equal "$x = $x - $x")
print $x; # line 4 (this prints a 6 (instead of zero))

Pay attention to line 3. Logically, you think it should be equivalent
to

$x = 3 - 3;

but instead the following happens:

Step 1 negates $x (so that it becomes -3 on BOTH sides of '-=').
Step 2 adds -3 and -3 (which makes -6) and puts that answer in $x.
Step 3 negates $x (making it become 6).

Clearly, the Math::BigInt and Math::BigInt modules are not expecting
their own objects to use the '-=' on themselves. If that should ever
happen, the object would double (instead of zero-ing out). To fix
this, either the '-=' operator should never be called with an object
and itself, or the following lines should be added to the
Math::BigInt::bsub() method:

if (overload::StrVal($x) eq overload::StrVal($y))
{
# The first and second objects are the same, so return zero:
return Math::BigInt->new(0);
}

Unfortunately, this fix may not work for NaN (Not a Number) values,
since NaN minus itself should return NaN, not zero (at least, I would
think that it should return NaN).

It looks like this bug only occurs when an object uses the '-='
operator (or the '/=' operator) on itself. This bug wouldn't happen
with the line:

$x -= $x - 3;

because ($x-3) gets evaluated first, returning a separate Math::BigInt
value, which won't interfere with the '-=' bug.

And if you think about it, there's really no reason to use lines
like:

$x -= $x; # logically should make $x zero
$x /= $x; # logically sets $x to 1 (assuming $x is non-zero)

because you already know what the answer should be, unless there is a
possibility that $x is NaN. (If you want to check to see if $x is
NaN, you should probably check BEFORE doing any math operations with
the $x->is_nan() method.)

To sum up, Peter, it looks like you found the only instance where
the '-=' fails. And since lines like "$x -= $x" were never expected
to be used, that special situation was never handled (causing a
classic bug).

Also note that some versions of Math::BigInt are implemented
differently than others (that is, the code is quite a bit different).
With one version of Perl, I can reproduce the exact same bug you have,
but with another version of Perl, "$x -= $x" correctly sets $x to
zero.

I hope this helps.

-- Jean-Luc Romano
 
P

Peter J. Acklam

Okay, I looked inside the code for Math::BigInt (...)

To sum up, Peter, it looks like you found the only instance where
the '-=' fails. And since lines like "$x -= $x" were never expected
to be used, that special situation was never handled (causing a
classic bug).

I am overwhelmed by you having put so much effort into this.
Thank you! You were correct assuming that NaN - NaN = NaN, not
zero, but there are two more cases which do not return zero:
Inf - Inf = NaN and -Inf - -Inf = NaN.
Also note that some versions of Math::BigInt are implemented
differently than others (that is, the code is quite a bit
different). With one version of Perl, I can reproduce the exact
same bug you have, but with another version of Perl, "$x -= $x"
correctly sets $x to zero.

I didn't have the latest version from CPAN

T/TE/TELS/math/Math-BigInt-1.70.tar.gz

I installed it and the bug is still there.
I hope this helps.

It helped a lot.

BTW, I sent a bug report yesterday.

Peter
 
J

J. Romano

Using the '-=' operator on a Math::BigInt object calls the bsub()
method, which subtracts two Math::BigInt objects by doing the
following:

1. It flips the sign of the second object (to negate it)
2. It adds the two objects together (with the badd() method)
3. It flips the sign of the second object again (to restore it)

Let me clarify a bit here. Suppose we had two Math::BigInt
variables:

my $a = Math::BigInt->new(5); # $a equals 5
my $b = Math::BigInt->new(1); # $b equals 1

and we used them together with the '-=' operator, like this:

$a -= $b;

The '-=' operator basically does the following three steps:

1. It temporarily negates $b:
$b = -$b; # $b is now -1
2. It adds $a and $b and puts the result in $a:
$a = $a + $b; # same as: $a = 5 + -1 (which equals 4)
3. It negates $b again to restore its original sign:
$b = -$b; # $b is now back to 1

If $x was declared with a line like:

my $x = Math::BigInt->new(3); # $x equals 3

You can see why the operator '-=' messes up with a line like:

$x -= $x;

by applying the same three steps to $x:

1. It temporarily negates $x:
$x = -$x; # $x is now -3
2. It adds $x and $x and puts the result in $x:
$x = $x + $x; # same as: $x = -3 + -3 (which equals -6)
3. It negates $x again to restore its original sign:
$x = -$x; # $x is now 6 (not zero)!

If you understood that, this should make more sense now:
Apparently the bsub() method assumes that the first and second object
are NOT the same. But since they are, when steps 1 and 3 get carried
out, both the first object AND the second object get their sign
flipped.

A better fix than the one I suggested last time would be to give
the second object a copy of the first if it's found that they are both
references to the same object:

if (overload::StrVal($x) eq overload::StrVal($y))
{
# The first and second objects are the same,
# so give the second a copy of the first:
$y = Math::BigInt->new($x);
}

This fix should go in the Math::BigInt::bsub() method right before the
sign of $y is flipped. Unlike the previous fix I suggested, this fix
should be able to handle NaN values. It might use a little more
overhead (by creating a new copy of a Math::BigInt), but that's fairly
negligible due to the fact that a line like "$x -= $x" rarely ever
gets called.

So that's the reason for the bug with the '-=' operator in
Math::BigInt. I'm not sure if Math::BigFloat inherits the same bug,
or if it's a different one altogether. Either way, I hope this clears
up some confusion (and some bugs!).

-- Jean-Luc Romano
 
J

J. Romano

I am overwhelmed by you having put so much effort into this.
Thank you!

You're very welcome! Comments in the code make finding bugs easier
(that's why I advise every coder to add in-code comments).
Fortunately, the Math::BigInt module is fairly commented (but, as
always, I wish there were more comments).
I didn't have the latest version from CPAN

T/TE/TELS/math/Math-BigInt-1.70.tar.gz

I installed it and the bug is still there.

Oh, sorry... I forgot to mention that the version I used that
didn't have the bug was a very old version. Let me check real
quick... the old version (without the bug) is $VERSION='0.01' (that's
quite an old version) whereas the more recent version (the one with
the bug) is $VERSION='1.60'.

The old version's Math::BigInt objects were simply references to
scalar strings, but the newer versions of Math::BigInt have objects
that are references to hashes, with "value" and "sign" as attributes.

I've been tinkering around a bit more with the Math::BigInt and
Math::BigFloat modules, and I discovered more things related to the
bug:

Here's another quick fix: Use a text editor to comment out the:

'-=' => sub { $_[0]->bsub($_[1]); },
'/=' => sub { scalar $_[0]->bdiv($_[1]); },

lines in the "use overload" pragma in the Math/BigInt.pm module. This
will prevent the '-=' and '/=' operators from being overloaded.
Instead they will be autogenerated from the '-' and the '/' operators.
Perl is smart enough to do this correctly, avoiding the bug found in
Math::BigInt::bsub().

In fact, the old version of Math::BigInt didn't overload any
assignment mutators (like '+=', '-=', '*=', '/=', etc.) because Perl
automagically autogenerated them.

(I originally found this confusing, even after reading "perldoc
overload". Therefore, two weeks ago I decided to study this a bit. I
posted a write-up of my findings on comp.lang.perl.misc on June 26,
2004 with the subject "Regarding copy constructors and mutators". I
expected to get some replies correcting me, suggesting advice, or
maybe even an occasional flame, but nobody replied. Maybe nobody
replied because there weren't any errors, or maybe because nobody
bothered to read it... Either way, studying this helped me find the
source of your bug.)

Anyway, removing (or commenting out) the '-=' and '/=' lines in the
"use overload" pragma of Math::BigInt will fix the problem in both
Math::BigInt and in Math::BigFloat (due to the fact that
Math::BigFloat inherits from Math::BigInt, as Anno said). This is
just a temporary fix, of course, since a comment near the top of the
BigInt.pm module explains that the assignment mutators are overloaded
as shortcuts for speed. In other words, they don't have to be
overloaded, but they are for efficiency. Unfortunately, this allowed
for the bug you found, Peter. The optimal fix would be to fix the bug
inside Math::BigInt::bsub() (and the one probably in
Math::BigInt::bdiv()).

I don't know if this reply explained anything more clearly or just
made things more confusing, but I'm posting it nevertheless.

Happy Perling,

-- Jean-Luc Romano
 

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

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top