perl5
perl5 copied to clipboard
The bignum module changes behavior of Math::BigInt::bdiv()
From [email protected]
Created by [email protected]
The documentation in "perldoc Math::BigInt" says that if $x is a Math::BigInt object, the code:
$x->bdiv($y);
will change the value of $x. That is exactly what I see when I run the following code:
#!/usr/bin/perl
use strict; use warnings;
use Math::BigInt; my $n = Math::BigInt->new(6);
print "\$n before \$n->bdiv(2): $n\n"; # prints 6 $n->bdiv(2); print "\$n after \$n->bdiv(2): $n\n"; # should print 3
# use bignum;
__END__
The output I get (on both Linux and Strawberry Perl for Windows) is:
$n before $n->bdiv(2): 6 $n after $n->bdiv(2): 3
Nothing wrong here. (This is exactly what I expect to see.)
However, if I uncomment the "use bignum;" line (that last line of the script, right before the __END__ tag) and re-run the code, I get this output (on both Linux and Strawberry Perl for Windows):
$n before $n->bdiv(2): 6 $n after $n->bdiv(2): 6
Notice that now the second line says that $n is 6. (It should say 3.)
Evidently just the inclusion of the "bignum" module is causing the Math::BigInt::bdiv() method to behave differently (in that it is not modifying the calling object).
I haven't found any other methods that are affected (such as Math::BigInt::bmul()), any other modules that are affected (such as Math::BigFloat), or any other offending modules that cause this problem (such as "bigint").
So the bug is that simply declaring "use bignum;" causes the Math::BigInt::bdiv() method to not modify the calling object.
Perl Info
Flags:
category=core
severity=low
Site configuration information for perl v5.8.4:
Configured by Debian Project at Wed May 10 04:14:05 UTC 2006.
Summary of my perl5 (revision 5 version 8 subversion 4) configuration:
Platform:
osname=linux, osvers=2.6.15.6, archname=i386-linux-thread-multi
uname='linux ernie 2.6.15.6 #1 thu mar 16 13:11:55 est 2006 i686 gnulinux '
config_args='-Dusethreads -Duselargefiles -Dccflags=-DDEBIAN -Dcccdlflags=-fPIC -Darchname=i386-linux -Dprefix=/usr -Dprivlib=/usr/share/perl/5.8 -Darchlib=/usr/lib/perl/5.8 -Dvendorprefix=/usr -Dvendorlib=/usr/share/perl5 -Dvendorarch=/usr/lib/perl5 -Dsiteprefix=/usr/local -Dsitelib=/usr/local/share/perl/5.8.4 -Dsitearch=/usr/local/lib/perl/5.8.4 -Dman1dir=/usr/share/man/man1 -Dman3dir=/usr/share/man/man3 -Dsiteman1dir=/usr/local/man/man1 -Dsiteman3dir=/usr/local/man/man3 -Dman1ext=1 -Dman3ext=3perl -Dpager=/usr/bin/sensible-pager -Uafs -Ud_csh -Uusesfio -Uusenm -Duseshrplib -Dlibperl=libperl.so.5.8.4 -Dd_dosuid -des'
hint=recommended, useposix=true, d_sigaction=define
usethreads=define use5005threads=undef useithreads=define usemultiplicity=define
useperlio=define d_sfio=undef uselargefiles=define usesocks=undef
use64bitint=undef use64bitall=undef uselongdouble=undef
usemymalloc=n, bincompat5005=undef
Compiler:
cc='cc', ccflags ='-D_REENTRANT -D_GNU_SOURCE -DTHREADS_HAVE_PIDS -DDEBIAN -fno-strict-aliasing -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64',
optimize='-O2',
cppflags='-D_REENTRANT -D_GNU_SOURCE -DTHREADS_HAVE_PIDS -DDEBIAN -fno-strict-aliasing -I/usr/local/include'
ccversion='', gccversion='3.3.5 (Debian 1:3.3.5-13)', gccosandvers=''
intsize=4, longsize=4, ptrsize=4, doublesize=8, byteorder=1234
d_longlong=define, longlongsize=8, d_longdbl=define, longdblsize=12
ivtype='long', ivsize=4, nvtype='double', nvsize=8, Off_t='off_t', lseeksize=8
alignbytes=4, prototype=define
Linker and Libraries:
ld='cc', ldflags =' -L/usr/local/lib'
libpth=/usr/local/lib /lib /usr/lib
libs=-lgdbm -lgdbm_compat -ldb -ldl -lm -lpthread -lc -lcrypt
perllibs=-ldl -lm -lpthread -lc -lcrypt
libc=/lib/libc-2.3.2.so, so=so, useshrplib=true, libperl=libperl.so.5.8.4
gnulibc_version='2.3.2'
Dynamic Linking:
dlsrc=dl_dlopen.xs, dlext=so, d_dlsymun=undef, ccdlflags='-Wl,-E'
cccdlflags='-fPIC', lddlflags='-shared -L/usr/local/lib'
Locally applied patches:
@INC for perl v5.8.4:
/etc/perl
/usr/local/lib/perl/5.8.4
/usr/local/share/perl/5.8.4
/usr/lib/perl5
/usr/share/perl5
/usr/lib/perl/5.8
/usr/share/perl/5.8
/usr/local/lib/site_perl
.
Environment for perl v5.8.4:
HOME=/home/jromano
LANG=en_US
LANGUAGE=en_US:en_GB:en
LD_LIBRARY_PATH (unset)
LOGDIR (unset)
PATH=/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin
PERL_BADLANG (unset)
SHELL=/bin/tcsh
From @pjf
On Thu Oct 29 10:21:18 2009, jl_post@hotmail.com wrote:
Evidently just the inclusion of the "bignum" module is causing the Math::BigInt::bdiv() method to behave differently (in that it is not modifying the calling object).
Ouch! Nice catch, that looks like a nasty bug. I can confirm it's still a problem under 5.10.0 and 5.10.1.
I'm marking this as something that needs attention in 5.12.0. Giving wrong results is bad.
Thanks again for the bug report, it's very much appreciated!
Paul
-- Paul Fenwick <pjf@perltraining.com.au> | http://perltraining.com.au/ Director of Training | Ph: +61 3 9354 6001 Perl Training Australia | Fax: +61 3 9354 2681
The RT System itself - Status changed from 'new' to 'open'
From @TJC
I confirmed this bug still exists in 5.11.1
From @iabyn
On Thu, Oct 29, 2009 at 10:21:18AM -0700, jl_post@hotmail.com (via RT) wrote:
#!/usr/bin/perl
use strict; use warnings;
use Math::BigInt; my $n = Math::BigInt->new(6);
print "\$n before \$n->bdiv(2): $n\n"; # prints 6 $n->bdiv(2); print "\$n after \$n->bdiv(2): $n\n"; # should print 3
# use bignum;
[snip]
However, if I uncomment the "use bignum;" line (that last line of the script, right before the __END__ tag) and re-run the code, I get this output (on both Linux and Strawberry Perl for Windows):
$n before $n->bdiv(2): 6 $n after $n->bdiv(2): 6
Notice that now the second line says that $n is 6. (It should say 3.)
Evidently just the inclusion of the "bignum" module is causing the Math::BigInt::bdiv() method to behave differently (in that it is not modifying the calling object).
It's actually a big in BigInt.pm or BigFloat.pm; the 'use bignum' just sets an internal flag, as can be seen below:
use Math::BigInt; use Math::BigFloat;
my $fn = Math::BigInt->new(6); $Math::BigInt::upgrade ='Math::BigFloat'; # side-effect of 'use bignum'; $fn->bdiv(2);
print "result (should be 3): $fn\n";
Jesse, given that Big* are CPAN modules and that this bug is present in 5.8.*, I can't see that this bug should be a 5.12 showstopper.
Also, does anyone know if there's a way to transfer this to the appropriate CPAN RT queue, since its not a core bug? Or does one just ask the originator to re-report it?
-- That he said that that that that is is is debatable, is debatable.
From @obra
On Wed Jan 20 14:13:43 2010, davem wrote:
Jesse, given that Big* are CPAN modules and that this bug is present in 5.8.*, I can't see that this bug should be a 5.12 showstopper.
It's not a regression, but I was certainly happy to take PJF's assertion that it's nasty enough to want a fix at face value.
Bugs in dual-lifed modules can certainly be showstoppers if they're nasty enough. Doing my own pass through this list an hour ago, I opted not to unlink as a showstopper yet, mostly in the hope that the dwindling size of the list would sucker some poor soul into providing us/tels with a fix. I don't feel particularly strongly about that, though. If you want to unlink it now, I won't object.
Also, does anyone know if there's a way to transfer this to the appropriate CPAN RT queue, since its not a core bug? Or does one just ask the originator to re-report it?
There is not. We've been talking to perl.org about merging the two, but we're not there yet.
From @nwc10
On Wed, Jan 20, 2010 at 10:13:10PM +0000, Dave Mitchell wrote:
Also, does anyone know if there's a way to transfer this to the appropriate CPAN RT queue, since its not a core bug? Or does one just ask the originator to re-report it?
I've been rather hoping that sd could be taught how to do this (specifically one way trips).
http://syncwith.us/sd/
Nicholas Clark
From @xdg
On Wed, Jan 20, 2010 at 5:19 PM, Jesse via RT <perlbug-followup@perl.org> wrote:
Also, does anyone know if there's a way to transfer this to the appropriate CPAN RT queue, since its not a core bug? Or does one just ask the originator to re-report it?
There is not. We've been talking to perl.org about merging the two, but we're not there yet.
One could forward the original email to the right RT queue at rt.cpan.org. And then "reply" to the new ticket with a link to the rt.perl.org ticket for further discussion.
David
From @obra
Based on previous discussion and input from former pumpkings, I'm removing this as a 5.12.0 blocker.
Back in October 2009 it was reported,
"Evidently just the inclusion of the "bignum" module is causing
the Math::BigInt::bdiv() method to behave differently (in that
it is not modifying the calling object)."
There was discussion as to the real cause of the problem.
I reported this problem upstream today at https://rt.cpan.org/Ticket/Display.html?id=150252. @pjacklam, can you take a look? Thanks.
The underlying cause here is upgrading and downgrading. Unlike the bigint, bigrat, and bigfloat pragmas, the bignum pragma enables upgrading and downgrading by default. The observed behaviour is how it always has been¹, and from what I understand, this is how the original author intended it to work.
Here is what happens: Division is an operation that might return a non-integer. So when upgrading is enabled in Math::BigInt, the division is handed over to the upgrade class. By default, Math::BigInt upgrades to Math::BigFloat. So it is Math::BigFloat that performs the division, using Math::BigFloat objects. When Math::BigFloat has performed a division, and downgrading is enabled, Math::BigFloat checks the result to see if it is an integer, and if it is, the result is passed to the downgrade class. By default, Math::BigFloat downgrades to Math::BigInt. So the final result is a Math::BigInt object. However, it will be a different object than the original invocand object.
The same thing happens with every Math::BigInt method that might return a non-integer, not just bdiv(). You get the same with bsqrt():
$x = Math::BigInt -> new(9); $x -> bsqrt(); # $x is still 9
I think the best solution is to improve the documentation and make it clear that this will happen, and why. The only way to make sure that the $x is never modified when upgrading and/or downgrading is enabled, is to re-bless $x from one class to another. This can be done, but from what I know of OOP, this is very bad practice. It would be possible to check whether the result can be represented exactly as an integer, and upgrade only when the result is not an integer. The downside of this, is that certain operations must be computed twice. But I am open for suggestions, corrections, arguments, … :)
¹ There were a few releases of bignum where I disabled upgrading and downgrading. This functionality was only partly implemented, according to the orginal author, and I had given up trying to understand how and when it was supposed to work. Eventually I got a grip of it and enabled it again.
The underlying cause here is upgrading and downgrading. Unlike the
bigint,bigrat, andbigfloatpragmas, thebignumpragma enables upgrading and downgrading by default. The observed behaviour is how it always has been¹, and from what I understand, this is how the original author intended it to work.Here is what happens: Division is an operation that might return a non-integer. So when upgrading is enabled in Math::BigInt, the division is handed over to the upgrade class. By default, Math::BigInt upgrades to Math::BigFloat. So it is Math::BigFloat that performs the division, using Math::BigFloat objects. When Math::BigFloat has performed a division, and downgrading is enabled, Math::BigFloat checks the result to see if it is an integer, and if it is, the result is passed to the downgrade class. By default, Math::BigFloat downgrades to Math::BigInt. So the final result is a Math::BigInt object. However, it will be a different object than the original invocand object.
The same thing happens with every Math::BigInt method that might return a non-integer, not just
bdiv(). You get the same withbsqrt():$x = Math::BigInt -> new(9); $x -> bsqrt(); # $x is still 9
@pjacklam, thank you for that explanation. I appreciate the time and effort you had to put into researching it.
I think the best solution is to improve the documentation and make it clear that this will happen, and why.
Yes, I think that some documentation about the existence of weird corner cases would be good.
The only way to make sure that the $x is modified when upgrading and/or downgrading is enabled, is to re-bless $x from one class to another. This can be done, but from what I know of OOP, this is very bad practice, ...
But this is exactly what already happens with:
D:\>perl -Mbignum -wle "$x = Math::BigInt->new(7); print ref($x); $x /= 2; print $x; print ref($x);"
Math::BigInt
3.5
Math::BigFloat
I personally think that's the correct behaviour, and I would expect that the exact same thing would happen with:
D:\>perl -Mbignum -wle "$x = Math::BigInt->new(7); print ref($x); $x->bdiv(2); print $x; print ref($x);"
Math::BigInt
7
Math::BigInt
But if we want to use bdiv() we instead need to do:
D:\>perl -Mbignum -wle "$x = Math::BigInt->new(7); print ref($x); $x = $x->bdiv(2); print $x; print ref($x);"
Math::BigInt
3.5
Math::BigFloat
To make it worse, these method calls will modify-in-place the object's value if there's no upgrading involved, but won't modify-in-place the value if upgrading is involved, UPDATE - 2 examples that demonstrate this:
D:\>perl -Mbignum -wle "$x = 7; $x->badd(2.5); print $x;"
7
D:\>perl -Mbignum -wle "$x = 7; $x->badd(2); print $x;"
9
This is all goes on silently, does not DWIM, and requires that the programmer be alert to these issues.
I know that ships have sailed and bridges have been burnt. I don't use bignum (and I haven't perused the docs,), but if I did use bignum I would hope that I use the overloaded operators rather than these troublesome method calls.
@pjacklam, if it's impractical to make any behavioural changes, then I agree that the bignum docs should prominently set out the perils of using the method calls.
Cheers, Rob
The only way to make sure that the $x is modified when upgrading and/or downgrading is enabled, is to re-bless $x from one class to another. This can be done, but from what I know of OOP, this is very bad practice, ...
But this is exactly what already happens with:
D:\>perl -Mbignum -wle "$x = Math::BigInt->new(7); print ref($x); $x /= 2; print $x; print ref($x);" Math::BigInt 3.5 Math::BigFloat
You got me there. I didn’t expect that. It must be perl itself that takes care of this, because /= is implemented as
'/=' => sub { scalar $_[0] -> bdiv($_[1]); },
When I look at the addresses, I see that they change:
$ perl -MScalar::Util=refaddr -Mbignum -wle \
'$x = Math::BigInt->new(7); print refaddr($x); $x /= 2; print $x; print refaddr($x);'
42949688776
3.5
42949904040
I personally think that's the correct behaviour, and I would expect that the exact same thing would happen with:
D:\>perl -Mbignum -wle "$x = Math::BigInt->new(7); print ref($x); $x->bdiv(2); print $x; print ref($x);" Math::BigInt 7 Math::BigInt
I agree. I think the fact that $x /= 2 and $x -> bdiv(2) give different results is counter-intuitive.
But if we want to use bdiv() we instead need to do:
D:\>perl -Mbignum -wle "$x = Math::BigInt->new(7); print ref($x); $x = $x->bdiv(2); print $x; print ref($x);" Math::BigInt 3.5 Math::BigFloat
Right. The code is full of cases like $x = $x -> bdiv($y) because – in general – I don’t know whether $x is modified in-place or not. I agree that this is not ideal.
To make it worse, these method calls will modify-in-place the object's value if there's no upgrading involved, but won't modify-in-place the value if upgrading is involved, UPDATE - 2 examples that demonstrate this:
D:\>perl -Mbignum -wle "$x = 7; $x->badd(2.5); print $x;" 7 D:\>perl -Mbignum -wle "$x = 7; $x->badd(2); print $x;" 9This is all goes on silently, does not DWIM, and requires that the programmer be alert to these issues.
Yes, I am aware of this. I believe the idea is that when you use bignum, it is to get transparent support for arbitrarily large numbers. In such cases, you don’t use method calls.
@pjacklam, if it's impractical to make any behavioural changes, then I agree that the bignum docs should prominently set out the perils of using the method calls.
I have been thinking about this, and I believe it is not as difficult to implement as I originally though. The question is how many other modules will break if I make this change. :-)
I believe the idea is that when you use
bignum, it is to get transparent support for arbitrarily large numbers. In such cases, you don’t use method calls.
Yes, I expect that's the usual practice. Otherwise there would surely have been more complaints about this issue than just this one report from 14 years ago.
I have been thinking about this, and I believe it is not as difficult to implement as I originally thought. The question is how many other modules will break if I make this change. :-)
I actually think not many, if any, breakages will occur. (Easy for me to say, I know ;-)
It should be a very rare case that someone does $y = $x->bdiv(2) and relies on $x not being modified.
If they are relying on $x not being modified, then they need to be certain that the bdiv(2) call initiates an upgrade or downgrade of the result.
But code like $x = $x->bdiv(2) or $x = $x->bsqrt() would not be broken. (It's just that those 2 pieces of code are catching the return value, when there would no longer be any need to do that.)
(BTW, I've been allowing cross-class overloading between my Math::GMPz, Math::GMPq and Math::MPFR modules for a while now - for reasons of "fun" rather than "usefulness". Lately, I've enhanced these operations to allow "upgrading", but I don't do any "downgrading". It's all done in the XS code - so perhaps of little relevance to the task facing you. And I don't really know whether my implementation of some of the "upgrading" overloaded operations leak memory ....)
Cheers, Rob
@sisyphus, @pjacklam, were there any changes made upstream in response to the discussion in this ticket? Are there any changes we need to make in blead?
I'm not sure if this is closable or not. I'll go along with @pjacklam's call on that.
Alas, this has not been fixed yet.
One thing that has only recently penetrated my skull is that the overloaded "op=" operators ('+/=', '/=', ...., '&=', '^=', etc.) are NOT in-place operations. They take the value that is returned to them. (Over the years I had invested some time and effort in getting them to work in-place .... always without success ;-) If you replace the overloading of Math::BigInt's '+=' operator:
'/=' => sub { scalar $_[0] -> bdiv($_[1]); },
with
'/=' => sub { scalar $_[0] -> bdiv($_[1]); 42},
then every time you do $mbi += $x (where $mbi is a Math::BigInt object), you'll find that $mbi changes to an IV with a value of 42.
OTOH, the overloading of the '++' and '--' operators is truly done in place. If you replace the overloading of Math::BigInt's '++' operator:
'++' => sub { $_[0] -> binc() },
with
'++' => sub { $_[0] -> binc(); return 42 },
then you'll find that the '++' overloading still works fine, because the return value is ignored and discarded.
I now believe that the ancients, in their wisdom, deliberately designed the "op=" operators that way because they recognized that there were occasions when something like $mbi += $x should result in $mbi changing from a Math::BigInt object to a different type of object (eg Math::BigRat or Math::BigFloat).
This opens the door to having $mbi += $mbf provide the exact same result as $mbf += $mbi, with it all being handled inside the overload '+=' sub, and without any additional interference.
Of course, implementing that change would probably result in a complaint from some idiot that prefers to work with a broken commutative law of addition. (Maybe best to wait until perl 7 for that type of change .... which means that we won't ever have to do anything about it.)
Similarly, there seems to be an assumption that (eg with overloading of addition) $mbi + $mbf must always return a Math::BigInt object and $mbf + $mbi must always return a Math::BigFloat object - which then also frequently breaks the commutative law of addition.
Again, I've not seen such a thing mandated in any perl documentation - and if it is mandated somewhere then I would argue that documentation should be removed.
To me, it makes sense to structure the overloading subs such that any overloaded operation involving a Math::BigFloat object always returns a Math::BigFloat object - even when it's Math::BigInt's or Math::BigRat's overload sub that has been called.
There's a couple problems being discussed that are somewhat independent.
First is that methods like ->bdiv modify their invocant. This was always a bad design, but is a documented feature of the module. As a bad design, it could remain, but it is entirely broken when used with the upgrade/downgrade mechanism. Likely the only solution here is to document that expecting the invocant to be modified when used with upgrading/downgrading will not work.
The second problem is that bignum has a global impact. While this ticket as reported is about bdiv, it impacts many other methods, and operating on explicitly created Math::BigFloat objects.
I can see two options for this, and possibly a combination of them.
bignum could limit its impact to only code using the module itself by using subclasses of Math::BigInt and Math::BigFloat which have different upgrading/downgrading behavior. Other code that used Math::BigInt/Math::BigFloat would then not be impacted by bignum being loaded. There could still be problems if different invocations of use bignum in a codebase tried to define different upgrading/downgrading behavior.
bignum could also set its upgrading/downgrading behavior lexically via %^H. The numbers constructed in a given scope could have internal upgrade/downgrade flags set based on these lexical values. Math::BigInt and Math::BigFloat have upgrade and downgrade methods that internally can set per-object values, but nothing appears to use them currently. The modules would need updates to properly support those internal flags.
Either of these changes could break some downstream code, but I expect any such code would currently be very fragile.
I'm willing to do some work on this if either of these solutions is acceptable.
I'm willing to do some work on this if either of these solutions is acceptable.
I've no objection to anything that will improve the behaviour. And some of this behaviour is pretty awful.
But I'm not so sure that the final result would justify the amount of effort invested ... I guess that's for you to assess ;-) (It would be simpler to just put a "deprecated" sticker on bignum.)
I never actually use bigint/bignum, though I often use the Math::Big* modules for quick and not-too-complicated checks.
I do have a clear notion of how the overloaded operators should work in cross-class calculations involving Math::BigInt/Math::BigFloat/Math::BigRat objects, but the implementation of that would break much existing code.
Even if deprecated, the existence of bignum at all with its current behavior is a problem.
I have a codebase where we are downpatching bignum to a version older than the perl release we are using, because if any module anywhere loads bignum, other code breaks. We're likely to add a global block on bignum to ensure that it is never loaded. It would be nicer if that kind of thing wasn't necessary.
I have a codebase where we are downpatching bignum to a version older than the perl release we are using, because if any module anywhere loads bignum, other code breaks.
Edited answer: I assume you are downpatching to one of the versions from 0.60 to 0.64, inclusive. Before 0.60, bignum behaved as it does now. I fixed it in version 0.60, but unfortunately I broke it in version 0.65. I'll fix this.
We are down patching to bignum 0.64. Here is a demonstration of the issue:
use strict;
use warnings;
use Test::More;
use Math::BigFloat;
for my $x (0,1) {
if ($x) {
# eval jail to capture compile time lexical effects
eval 'use bignum; 1;' or die $@;
}
subtest +($x ? "after" : "before") . ' bignum' => sub {
my $num = Math::BigFloat->new("2468", undef, undef);
$num = Math::BigFloat->bfround($num, -3, 'common');
cmp_ok($num, 'eq', '2468.000', "BigFloat Int");
$num = Math::BigFloat->new("2468.1", undef, undef);
$num = Math::BigFloat->bfround($num, -3, 'common');
cmp_ok($num, 'eq', '2468.100', "BigFloat Float");
};
}
We are down patching to bignum 0.64. Here is a demonstration of the issue: […]
I had a look at bignum’s changelog. I see that versions 0.60 to 0.64 are different in the sense that that upgrading and downgrading is disabled by default. The upgrading/downgrading behaviour was inconsistent, and I wasn’t sure how it was supposed to be working, so I decided to disable it. In 0.65 I reintroduced upgrading and downgrading – and started implementing it the way I think it is supposed to be working. At the same time, I introduced the bigfloat pragma, which is essentially bignum without upgrading and downgrading. Also, the bigfloat pragma is the Math::BigFloat equivalent of the bigint and bigrat pragmas.
If you replace bignum with bigfloat in your code above, you will see that it behaves as you expect.
The relevant code (which was actually in a CPAN module) has already been changed. But since bignum has a global impact, any code anywhere (in a very large codebase) could reintroduce the problem. That isn't acceptable, so we will either need to blacklist the module to ensure it is never added, or fix bignum in some way.
There's a couple problems being discussed that are somewhat independent. First is that methods like ->bdiv modify their invocant. This was always a bad design ....
This has been puzzling me for a while - perhaps it's just a misunderstanding on my part. In a "use bignum" environment, operations involving both a non-integer Math::BigFloat object ($mbf) and a Math::BigInt object ($mbi) will only be sane if the Math::BigInt object is upgraded to a Math::BigFloat object. Otherwise, basics like the commutative laws of addition and multiplication are broken. Similarly for subtraction and division:
- the condition ($mbf - $mbi) == -($mbi - $mbf) would not be met;
- the condition ($mbf/$mbi) == 1/($mbi/$mbf) would not be true, even when allowing for the fact that the RHS is rounded twice, whereas the LHS is rounded only once;
Also we would find that ($mbf += $mbi) == ($mbi += $mbf) would not be met.
I do agree that bdiv should not perform such a modification - for mine, that would be done only by the Math::BigFloat and Math::BigInt overloadings of the arithmetic operators, but Math::BigInt->new(6)->bdiv(Math::BigFloat->new(3.99)) should always return a Math::BigInt object with a value of 2.
Yet, in a bignum environment we expect 6 / 3.99 to return a Math::BigFloat value of 1.503759... .
Of course, the overloading of those arithmetic operators, as performed by Math::BigFloat/Math::BigInt, does not satisfy those bignum requirements... so what then ?
In the "use bignum" environment, I see no place for downgrading (from BigFloat to BigInt) at all. Such a thing is reserved for the "use bigint" environment, where BigFloats should be truncated to BigInts - and an upgrade in the opposite direction has no place.
One side note to the upgrading of a Math::BigInt object to a Math:BigFloat object is that precision will be lost if the precision of the BigInt is greater than the precision of the BigFloat - a trap for the unwary. Of course, the same thing exists in perl itself when NV precision is less than IV precision. On a perl whose nvtype is double && ivsize is 8:
perl -wle "$x = ~0; $y = $x - 10.5; print $x; print $y;"
18446744073709551615
1.84467440737096e+19
We subtracted 10.5 from ~0 and apparently ended up with an even larger number.
BTW, I have no issue with the maintainers (past and present) of Math::BigFloat, Math::BigInt, bignum and bigint.
Sure, those modules don't behave as I would like - but it has taken me 20 years to devise a scenario with which I'm comfortable.
Over that period, from time to time, I could probably would have presented conflicting views.
Even if we all agreed to alter the Math::BigInt/Math::BigFloat overloading such that it did meet the needs of "bignum", we wouldn't be able to make those changes because of the amount of code that would be broken.
Seems to me that @haarg's proposed "down patching" fixes the demo he provided because a downgrade of the Math::BigFloat object was not performed - and I'm totally on board with the removal of such downgrades under "bignum". They have no place there.
The main issue here (the behaviour of bdiv()) has been fixed in Math-BigInt version 2.003004, which I have just now uploaded to PAUSE.
The relevant code (which was actually in a CPAN module) has already been changed. But since bignum has a global impact, any code anywhere (in a very large codebase) could reintroduce the problem. That isn't acceptable, so we will either need to blacklist the module to ensure it is never added, or fix bignum in some way.
I believe the issue is not with bignum, but with Math::Big(Int|Float|Rat), which use global parameters rather than instance variables. There is a CPAN RT requesting the global parameters be moved into the OO interface. I have started looking into this. If you see any improvements to bignum that will help, feel free to send me a patch.
This has been puzzling me for a while - perhaps it's just a misunderstanding on my part. In a "use bignum" environment, operations involving both a non-integer Math::BigFloat object ($mbf) and a Math::BigInt object ($mbi) will only be sane if the Math::BigInt object is upgraded to a Math::BigFloat object.
But the Math::BigInt objects are upgraded to Math::BigFloat, if necessary.
Otherwise, basics like the commutative laws of addition and multiplication are broken. Similarly for subtraction and division:
- the condition ($mbf - $mbi) == -($mbi - $mbf) would not be met; […]
But this condition is met:
use strict;
use warnings;
use bignum;
my $mbi = Math::BigInt -> new("3");
my $mbf = Math::BigFloat -> new("4.5");
print $mbf - $mbi, "\n"; # prints 1.5
print -($mbi - $mbf), "\n"; # also prints 1.5
I do agree that bdiv should not perform such a modification - for mine, that would be done only by the Math::BigFloat and Math::BigInt overloadings of the arithmetic operators,
The bxxx methods (badd, bsub, bmul, bneg, babs, bpow, …) have always modified their invocand. I fear it is too late to change that now.
but
Math::BigInt->new(6)->bdiv(Math::BigFloat->new(3.99))should always return a Math::BigInt object with a value of 2.
But it does, except when upgrading is enabled. If you don’t want upgrading, don’t use bignum. I fail to see to see the problem. :-/
Yet, in a bignum environment we expect
6 / 3.99to return a Math::BigFloat value of 1.503759... . Of course, the overloading of those arithmetic operators, as performed by Math::BigFloat/Math::BigInt, does not satisfy those bignum requirements... so what then ?
I am confused. Which requirements are not satisfied? With bignum, you do ineed get 1.503759…
$ perl -Mbignum -wle 'print 6 / 3.99'
1.503759398496240601503759398496240601504
In the "use bignum" environment, I see no place for downgrading (from BigFloat to BigInt) at all. Such a thing is reserved for the "use bigint" environment, where BigFloats should be truncated to BigInts - and an upgrade in the opposite direction has no place.
But the whole purpose of`bignum is to automatically upgrade and downgrade between Math::BigInt objects and Math::BigFloat objects.
By the way, there is no downgrading in bigint. That is, at no point is a Math::BigFloat converted to a Math::BigInt internally when bigint is in effect. Any finite numeric literal that is not an integer is truncated to an integer and then converted to a Math::BigInt object, but Math::BigFloats are never involved.