Skip Menu |

This queue is for tickets about the Math-MPFR CPAN distribution.

Report information
The Basics
Id: 133635
Status: resolved
Priority: 0/
Queue: Math-MPFR

People
Owner: Nobody in particular
Requestors: pjacklam [...] gmail.com
Cc:
AdminCc:

Bug Information
Severity: (no value)
Broken in:
  • 4.13
  • 4.14
Fixed in: (no value)



Subject: = does not copy as documentation says
The documentation says about operator overloading: = (The copy has the same precision as the copied object.) but apparently, "=" does not return a copy. Consider the following code: use strict; use warnings; use Math::MPFR qw< :mpfr >; my $RND = MPFR_RNDN; # rounding mode my $PREC = 120; # precision Rmpfr_set_default_prec($PREC); print "\nDefault precision is ", Rmpfr_get_default_prec(), "\n"; # Compute pi to 120 bits. my $pi0 = Rmpfr_init(); # initialize Rmpfr_const_pi($pi0, $RND); # compute pi print "pi0 = ", $pi0, "\n"; # print it # Round to 20 (binary) digits. my $pi1 = $pi0; # make a copy Rmpfr_prec_round($pi1, 20, $RND); # round to 20 bits print "pi1 = ", $pi1, "\n"; # print it # Print the original value. print "pi0 = ", $pi0, "\n"; # why is this rounded? The code illustrates that rounding a copy does also round the original value. The output is Default precision is 120 pi0 = 3.1415926535897932384626433832795028847 pi1 = 3.1415939 pi0 = 3.1415939 However, if "=" returned a true copy, the original value should not have been rounded.
On Sat Oct 31 05:41:51 2020, pjacklam wrote: Show quoted text
> The documentation says about operator overloading: > > = (The copy has the same precision as the copied object.) > > but apparently, "=" does not return a copy. Consider the following code: > > use strict; > use warnings; > > use Math::MPFR qw< :mpfr >; > > my $RND = MPFR_RNDN; # rounding mode > my $PREC = 120; # precision > > Rmpfr_set_default_prec($PREC); > print "\nDefault precision is ", Rmpfr_get_default_prec(), "\n"; > > # Compute pi to 120 bits. > > my $pi0 = Rmpfr_init(); # initialize > Rmpfr_const_pi($pi0, $RND); # compute pi > print "pi0 = ", $pi0, "\n"; # print it > > # Round to 20 (binary) digits. > > my $pi1 = $pi0; # make a copy > Rmpfr_prec_round($pi1, 20, $RND); # round to 20 bits > print "pi1 = ", $pi1, "\n"; # print it > > # Print the original value. > > print "pi0 = ", $pi0, "\n"; # why is this rounded? > > The code illustrates that rounding a copy does also round the original > value. The output is > > Default precision is 120 > pi0 = 3.1415926535897932384626433832795028847 > pi1 = 3.1415939 > pi0 = 3.1415939 > > However, if "=" returned a true copy, the original value should not > have been rounded.
It looks to me that this demonstrates a trap in creating a copy via the overloading of the "=" operator. It's also a trap of which I was unaware - thanks for bringing it to my attention. The overload documentation says: An object, however, is a reference to blessed data, so if $a and $b are objects then the assignment "$a = $b" copies only the reference, leaving $a and $b referring to the same object data. One might therefore expect the operation "--$a" to decrement $b as well as $a. However, this would not be consistent with how we expect the mathematical operators to work. Perl resolves this dilemma by transparently calling a copy constructor before calling a method defined to implement a mutator ("--", "+=", and so on.). In the above example, when Perl reaches the decrement statement, it makes a copy of the object data in $a and assigns to $a a reference to the copied data. Only then does it call "decr()", which alters the copied data, leaving $b unchanged. Thus the object metaphor is preserved as far as possible, while mathemagical operations still work according to the arithmetic metaphor. There's more in the "Copy Constructor" section of the docs, including: The subroutine for '=' does not overload the Perl assignment operator: it is used only to allow mutators to work as described here. (See "Assignments" above.) AIUI, when you assign "$x = $y;" both $x and $y will refer to the same object until an operation that could change the value of one of those scalars is detected. For example, for your script to DWIM, instead of doing: $pi1 = $pi0; do $pi1 = $pi0; $pi1 += 0; # now $pi1 and $pi0 refer to separate objects And, apparently it has to be an operation that the overload pragma recognizes as being one that can change the value. It won't work if you call "Rmpfr_add_ui($pi1, $pi1, 0, MPFR_RNDN);" instead of "$pi1 += 0;". Obviously, Rmpfr_prec_round() doesn't trigger the split. Neither would many other functions, including Rmpfr_trunc(), Rmpfr_ceil() and Rmpfr_floor(). Math::MPFR is behaving here in essentially the same way as Math::GMP and Math::BigInt. The following script demonstrates that with Math::GMP, Math::BigInt and Math::MPFR, $x and $y refer to the same object until the "$y += 0;" call is reached. use strict; use warnings; use Math::GMP; use Math::BigInt; use Math::MPFR; use Devel::Peek; my $x = Math::BigInt->new(42); # my $x = Math::GMP->new(42); # my $x = Math::MPFR->new(42); my $y = $x; # $x and $y still refer to the same object Dump($x); Dump($y); $y += 0; # $y now refers to a separate object Dump($y); And, I believe, Math::BigFloat presents the very same trap as Math::MPFR use strict; use warnings; use Math::BigFloat; my $x = Math::BigFloat->new(1 / 3); print "$x\n"; # prints 0.333333333333333 my $y = $x; print "$y\n"; # prints 0.333333333333333 # $y += 0; # This will make script DWIM $y->bround(4); print "$y\n"; # prints 0.3333 - as expected print "$x\n"; # prints 0.3333 - doesn't DWIM Now ... it could be that there's some action I could take in the Math::MPFR source that would remove this trap. Or maybe there's some documentation that should be added to raise awareness of it. I don't know ... and I'm more than happy to consider thoughts on those aspects. I think that, in Math::MPFR at least, there are safer ways to copy - eg Rmpfr_set(), Rmpfr_setsign(), Rmpfr_copysign() or even "$y = Math::MPFR->new($x);". Admittedly, they all lack the convenience of doing "$y = $x;". Cheers, Rob
I shamefully admit that I haven't had the full understanding of how overloading and assignments work. As you illustrate, the case I ran into is not special for Math::MPFR, but applies to Perl objects in general. I think this ticket can be closed. I'll use something like the following, which is sufficient for my needs: sub clone { my $x = shift; my $y = Rmpfr_init(); Rmpfr_set($y, $x, $RND); return $y; }
On Sun Nov 01 04:07:52 2020, pjacklam wrote: Show quoted text
> I shamefully admit that I haven't had the full understanding of how > overloading and assignments work.
Oh, there's nothing to be ashamed of. I had no idea that this trap existed, and I'm genuinely thankful that you took the time and effort to raise this issue. Show quoted text
> As you illustrate, the case I ran into > is not special for Math::MPFR, but applies to Perl objects in general.
I'm thinking it's a bug in perl's overloading model. It's ok to defer the split until one of $x or $y changes, but to then not detect that a change has occurred is (frankly) unacceptable. I don't know whether it is fixable. I might yet file a bug report for this against perl ... though the response (at best) is likely to be "patches welcome". Show quoted text
> I think this ticket can be closed.
Yes, I'll get around to doing that. Show quoted text
> I'll use something like the following, which is sufficient for my needs: > > sub clone { > my $x = shift; > my $y = Rmpfr_init(); > Rmpfr_set($y, $x, $RND); > return $y; > }
That should do it ok. Of course, if the precision of $x is greater than the default precision, then you'll need: my $y = Rmpfr_init2(Rmpfr_get_prec($x)); Cheers, Rob
On Sun Nov 01 06:59:14 2020, SISYPHUS wrote: Show quoted text
> I'm thinking it's a bug in perl's overloading model. > It's ok to defer the split until one of $x or $y changes, but to then > not detect that a change has occurred is (frankly) unacceptable.
I fully agree. When "=" is overloaded to return a true copy, the current behaviour is counter-intuitive. In my opinion it doesn't adhere to the DWIM philosophy of Perl. Show quoted text
> I don't know whether it is fixable. I might yet file a bug report for > this against perl ... though the response (at best) is likely to be > "patches welcome".
I still think it might be worth the while. Show quoted text
> > I'll use something like the following, which is sufficient for my > > needs: > > > > sub clone { > > my $x = shift; > > my $y = Rmpfr_init(); > > Rmpfr_set($y, $x, $RND); > > return $y; > > }
> > That should do it ok. Of course, if the precision of $x is greater > than the default precision, then you'll need: > > my $y = Rmpfr_init2(Rmpfr_get_prec($x));
Oh, right. Of course. Thanks. Cheers, Peter
On Tue Nov 03 04:23:46 2020, pjacklam wrote: Show quoted text
> On Sun Nov 01 06:59:14 2020, SISYPHUS wrote: >
> > I'm thinking it's a bug in perl's overloading model. > > It's ok to defer the split until one of $x or $y changes, but to then > > not detect that a change has occurred is (frankly) unacceptable.
> > I fully agree. When "=" is overloaded to return a true copy, the current > behaviour is counter-intuitive. In my opinion it doesn't adhere to the > DWIM philosophy of Perl. >
> > I don't know whether it is fixable. I might yet file a bug report for > > this against perl ... though the response (at best) is likely to be > > "patches welcome".
> > I still think it might be worth the while. >
> > > I'll use something like the following, which is sufficient for my > > > needs: > > > > > > sub clone { > > > my $x = shift; > > > my $y = Rmpfr_init(); > > > Rmpfr_set($y, $x, $RND); > > > return $y; > > > }
> > > > That should do it ok. Of course, if the precision of $x is greater > > than the default precision, then you'll need: > > > > my $y = Rmpfr_init2(Rmpfr_get_prec($x));
> > Oh, right. Of course. Thanks. > > Cheers, > Peter
Thinking about it a bit more, it seems that the behaviour is as documented - which means it's (technically) not a bug. It certainly doesn't DWIM, so one could possibly make a "feature request" that the flaw be fixed. However, I would expect that if such a fix was readily achievable, then it would have been built into the original design in the first place. Apparently the author of the documentation was well aware of the shortcomings. I won't be pursuing this any further. For Math::MPFR, I think the smart thing to do is to take a leaf out of Math::GMP's approach and include no reference whatsoever to overloading "=" in either the source or the documentation. The same overloading behaviour will still exist (at least that's the case with Math::GMP), but Math::MPFR will be absolved of all responsibility for what happens when a user does "$x = $y;", where $y is a Math::MPFR object. Cheers, Rob
After more pondering and some relevant discussion at https://www.perlmonks.org/?node_id=11123464 I've opted for recommending caution (in the "OPERATOR OVERLOADING" section of the Math::MPFR pod) when overloading '='. This change has been pushed to https://github.com/sisyphus/math-mpfr, and will appear in the next release (Math-MPFR-4.15). I've also taken the same action in all of my other modules on github that overload '='. Cheers, Rob