Re: Surviving 'Illegal division by zero'
by Abigail-II (Bishop) on Jun 23, 2004 at 11:37 UTC
|
use strict;
use warnings;
{
package MyNumber;
use overload '0+' => \&numify,
'/' => \&division;
use Scalar::Util;
my %numbers;
BEGIN {*MyNumber::__ = \&Scalar::Util::refaddr}
sub DESTROY {delete $numbers {__ shift}}
sub new {my $f; bless \$f => shift}
sub set {$numbers {__ $_ [0]} = $_ [1]; $_ [0]}
sub numify {$numbers {__ $_ [0]}}
sub division {my $f = $numbers {__ $_ [0]};
my $s = ref ($_ [1]) =~ /MyNumber/ ? $numbers {__ $_
+ [1]}
: $_ [1];
($f, $s) = ($s, $f) if $_ [2];
$s ? $f / $s : undef}
}
my $fig_1 = MyNumber -> new -> set (get_numeric_value_from_xml (...))
+;
my $fig_2 = MyNumber -> new -> set (get_numeric_value_from_xml (...))
+;
my $growth = 100 * $fig_1 / $fig_2 - 100;
Abigail | [reply] [d/l] |
|
++ Thanks, that's a really interesting answer, though I think a simple function (see above) might be easier for whoever maintains my library to understand at first glance ;). It's also a shame that it isn't possible to directly re-open the method definitions of '/' and '+' for numeric scalars in Perl.
As an aside, does this solution using numeric classes make anyone else pine for them in Perl (Float, Integer, Complex ...)?
| [reply] |
|
This will make your code easier to understand. And, this is the way to "directly re-open the method definitions". In fact, it's safer to do it this way than it is to redefine it for the whole program. That kind of "action-at-a-distance" is the source of more maintenance nightmares than anything else.
Another way to look at it is that you can now parse, format, and handle anything to do with numbers in one place. If you want to change how you deal with numeric values, simply change the class. There's your global change, but it's nicely encapsulated.
A last concept to leave you with - if someone were to take your code and wrap it in something else, which is the politer way to handle things?
------
We are the carpenters and bricklayers of the Information Age.
Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose
I shouldn't have to say this, but any code, unless otherwise stated, is untested
| [reply] |
|
|
|
What about the following syntactic sugar?
sub new {my $f; bless \$f => shift; $f -> set(@_) if @_; $f}
That would allow the following modification:
my $fig_1 = MyNumber -> new (get_numeric_value_from_xml (...));
I only propose it because most constructors also allow for values to be passed in, which keeps to the Principle of Least Surprise.
------
We are the carpenters and bricklayers of the Information Age.
Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose
I shouldn't have to say this, but any code, unless otherwise stated, is untested
| [reply] [d/l] [select] |
|
I only propose it because most constructors also allow for values to be passed in, which keeps to the Principle of Least Surprise.
I don't like constructors that are also intializers - constructors that are initializers make MI harder than it should be. I do have some coding guidelines (posted somewhere on this site), and there I explain I don't initialize objects from the constructor for exactly that reason - making MI harder (of course, if your object is anything other than "just a blessed reference", MI is doomed anyway).
And sure allowing arguments to the constructor, and having the constructor call the initializer if arguments are given works, but it makes the constructor less simple, and hence, less elegant.
The "everyone else does it" argument doesn't work for me, or else I had used blessed hashrefs for objects instead of just a blessed reference.
Abigail
| [reply] |
Re: Surviving 'Illegal division by zero'
by adrianh (Chancellor) on Jun 23, 2004 at 11:11 UTC
|
$growth = eval { ( $fig_2 / $fig_1 * 100 ) - 100 };
| [reply] [d/l] [select] |
|
Thanks, but I've got lots and lots of these calculations, and what I'd like to be able to do is scope-globally adjust the behaviour, rather than have to wrap every block in either an eval or a conditional.
On reflection, a function mydiv:
sub mydiv {
my ( $divided, $divisor ) = @_;
$divisor ? $divided / $divisor : undef;
}
my $growth = mydiv($fig_2, $fig_1);
Might have been easier, but Abigail-II's answer is another way of getting to it as well.
Thanks
Alex | [reply] [d/l] |
Re: Surviving 'Illegal division by zero'
by gellyfish (Monsignor) on Jun 23, 2004 at 11:12 UTC
|
eval
{
$growth = ( $fig_2 / $fig_1 * 100 ) - 100;
};
$growth will be undef if the expression dies.
/J\
| [reply] [d/l] [select] |
Re: Surviving 'Illegal division by zero'
by wufnik (Friar) on Jun 23, 2004 at 14:40 UTC
|
eval is fine for some things, but
if i were dealing with a lot of data here, it might
be of interest to me to consider 0 values as 'very
very small' values, and transpose the problem to
the numerical sphere.
the relevance of this approach depends on your data:
but eval can be costly if i were dealing with mines
of it. in addition, using a numerical approach allows you to 'take in' the points around 0 if you are creating a distribution, for instance - which stripping them out with an eval does not.
so, simply: transform the data by a adding a wee wee float,
apply your calculation, then strip out aberrant $growths - if you are not fond of them. ie:
my $fig_1 = get_numeric_value_from_xml(...);
my $fig_2 = get_numeric_value_from_xml(...);
my $growth;
my $ff = 1.175E-38;
$growth = ( $fig_2 / ($fig_1 + $ff) * 100 ) - 100;
don't hold me responsible for this choice of $ff. you
may have to adjust this to another (bigger) small figure
depending on your data.
cya
just treat 0 as
...wufnik
-- in the world of the mules there are no rules --
| [reply] [d/l] [select] |
Re: Surviving 'Illegal division by zero'
by andyf (Pilgrim) on Jun 23, 2004 at 16:57 UTC
|
The best value for Wufniks $ff (the fuzz factor or often epsilon) is given in POSIX. From
Mastering Algorithms with Perl (the wolf book pp.472 - 474)
If you have the POSIX module you may use the DBL_EPSILON constant that it defines:
use POSIX;
use constant epsilon => 100 * DBL_EPSILON;
Very useful in dsp to write machine independent code. You dont't want to find it all breaks when you port it somewhere else. Although its messier in just one place Abigails choice of overloading the operator is probably the most elegant general solution imo.
| [reply] [d/l] |
Re: Surviving 'Illegal division by zero'
by ambrus (Abbot) on Jun 23, 2004 at 12:09 UTC
|
As others have said, eval is your friend.
I have used eval once to catch division by zero, see Re: Stereotypes about perl. Here's the relevant snippet from it:
eval {
drr (
@$zb-1-$p[2][1], @$zb-1-$p[3][1],
$p[2][0], $p[2][0],
-($p[3][0]-$p[2][0])/($p[3][1]-$p[2][1]),
-($p[1][0]-$p[2][0])/($p[1][1]-$p[2][1]),
$p[2][2],
-($p[3][2]-$p[2][2])/($p[3][1]-$p[2][1]),
($p[1][2]-$p[3][2])/($p[1][0]-$p[3][0]),
$col, $zb
);
}; $@ and do {
$@=~m/division by zero/ or die $@;
};
| [reply] [d/l] |
Re: Surviving 'Illegal division by zero'
by SirBones (Friar) on Jun 23, 2004 at 15:29 UTC
|
Somewhat off topic, I know, but I just have to say how cool this site is for a Perl newbie. Just last night I was drifting off thinking that I need to find something like a try{}/catch{} block to grab certain exceptions (including divide by zero) in a section of code I'm migrating from Java. Before I could look anything up this morning I bumped into this node. I'm reading the relevant Camel Book entry as I speak...or type...well not really, but you see the point. I can't imagine a better online Perl resource than this joint.
Cheers,
Ken
"This bounty hunter is my kind of scum: Fearless and inventive." --J.T. Hutt
| [reply] |
Signals and floating point exceptions
by sleepingsquirrel (Hermit) on Jun 23, 2004 at 22:47 UTC
|
My first thought was that you should be able to use signals to catch these types of floating point exceptions...
$SIG{FPE}=sub{...};
...but it turns out that perl is too smart and catches these types of errors before passing them on to the hardware. Does anyone know if there is a compiler flag for perl which would disable these checks? Just for the fun of it I compiled up a version of perl which commented out the divide by zero check (in pp.c)...
/*if (right == 0.0)
DIE(aTHX_ "Illegal division by zero");*/
...make, compile, run...
$ ./perl -e '$a=1.23/0.00;print "\$a=$a\n"'
$a=inf
...looks like we're going down the right track...
if (right == 0.0)
PUSHs(&PL_sv_undef);/*DIE(aTHX_ "Illegal division by zero");*/
else
PUSHn( left / right );
...make, compile, rerun...
$ ./perl -e 'print "UNDEFINED\n" unless (defined(1.23/0.00))'
UNDEFINED
Of course you'd probably want to take care of the integer division case also.
-- All code is 100% tested and functional unless otherwise noted.
| [reply] [d/l] [select] |
|
| [reply] |
Re: Surviving 'Illegal division by zero'
by zakzebrowski (Curate) on Jun 23, 2004 at 20:58 UTC
|
I ran into a similar situation at work today actually, however, I wasn't trying to do a mathimatical situation. Since I'm bored, I'll share. What I was trying to do is load a file into memory, and then perform a subsistuion, similar to $line =~ s/\n//g; , and then I was going to perform additional string subsitutions on the line. This didn't work and resulted in a "illegal division by zero" error, so I needed a different work around. I thought of subsituting a different character for \n, but that character could exist in the file, so I backed off from that approach. I never really did find out a good way to do it, so instead I made a seperate regular expression which would catch the cases where a \n was embeded in the the other regular expression I wanted to extract from the file. I think this is the more elegent and robust solution, since I later found additional special cases that I wasn't taken into effect...
| [reply] [d/l] |