http://qs1969.pair.com?node_id=369002

ViceRaid has asked for the wisdom of the Perl Monks concerning the following question:

Hi

I've got a fairly chunky library that parses a big XML file full of figures. As well as grabbing the numbers out of the appropriate bits, it also calculates figures for growth percentages. Once I remembered to coerce the strings from the XML into figures (so '0.000' == 0 ), I still have to check every time before I do a division that the amount I'm dividing by isn't 0:

my $fig_1 = get_numeric_value_from_xml(...); my $fig_2 = get_numeric_value_from_xml(...); my $growth; if ( $fig1 != 0 ) { $growth = ( $fig_2 / $fig_1 * 100 ) - 100; }

If I don't check every time before I do a calculation like this, I end up dying of 'Illegal division by zero' whenever $fig_1 is zero. I was wondering if there's some way I can alter this behaviour - preferably only in the scope of this file - so that instead of dying, a division by zero simply returns undef?

TIA
Alex

Replies are listed 'Best First'.
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

      ++ 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 ...)?

        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

      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

        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

Re: Surviving 'Illegal division by zero'
by adrianh (Chancellor) on Jun 23, 2004 at 11:11 UTC
    . I was wondering if there's some way I can alter this behaviour

    You could wrap the expression in an eval

    $growth = eval { ( $fig_2 / $fig_1 * 100 ) - 100 };

      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

Re: Surviving 'Illegal division by zero'
by gellyfish (Monsignor) on Jun 23, 2004 at 11:12 UTC

    eval BLOCK would be your friend here:

    eval { $growth = ( $fig_2 / $fig_1 * 100 ) - 100; };
    $growth will be undef if the expression dies.

    /J\

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 --
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.
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 $@; };
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
Signals and floating point exceptions
by sleepingsquirrel (Chaplain) 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.

      Actually, having a pragmata that changed the behavior on division by zero from death to just letting the floating point hardware do with it as it will would be rather interesting. I think it should even be possible to do it without slowing up the works except when division by zero actually happens.

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...


    ----
    Zak - the office