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

I'm encountering a case when minmax() sometimes returns an undef max. So far I haven't been able to create a simpler test case which reproduces this problem, but this happens when I'm testing my Bencher module with Bencher::Scenario::AcmePERLANCARTestPerformance:

% bencher -m AcmePERLANCARTestPerformance Use of uninitialized value $max in numeric le (<=) at lib/Bencher/Form +atter/ScaleTime.pm line 28.

The offending code is (starting from line 25 in ScaleTime.pm):

my ($min, $max) = minmax(map {$_->{time}} @{$envres->[2]}); my ($unit, $factor); if ($max <= 1.5e-6) {

Dumping the variables shows that minmax() is getting (0.002) (a single-element list with the element value of 0.002). The result from minmax is (0.002, undef).

This does not happen when the input list is of different value, e.g.: (0.0023), (0.0019), (0.0022), and so on. Only when the value is (0.002) on my computer.

This does not happen when I change the above code into:

my @list = map {$_->{time}} @{$envres->[2]}; my ($min, $max) = minmax(@list); ...

This does not happen when I try to reproduce this in a simple test program, using the same value of $envres.

This does not happen if I use the List::MoreUtils::PP backend (by setting LIST_MOREUTILS_PP environment variable to 1).

Any clue?

UPDATE 1: I'm now able to create a simple test to reproduce the bug. This is still List::MoreUtils 0.413. It seems related to floating point rounding in sprintf() (e.g. using %f or %g format).

% perl -MList::MoreUtils=minmax -MData::Dump -e'for(1..20) { my ($min, + $max) = minmax(sprintf("%.4g", rand())); dd ($min, $max) }' (0.9403, 0.9403) (0.2669, 0.2669) (0.4618, 0.4618) (0.6728, 0.6728) (0.829, undef) (0.6572, 0.6572) (0.7323, 0.7323) (0.521, undef) (0.03817, 0.03817) (0.9032, 0.9032) (0.8139, 0.8139) (0.8573, 0.8573) (0.9723, 0.9723) (0.7832, 0.7832) (0.7387, 0.7387) (0.06714, 0.06714) (0.127, undef) (0.6433, 0.6433) (0.02692, 0.02692) (0.157, undef)

I'm able to reproduce this on perl 5.22.1, 5.22.0, 5.20.3. perls 5.18.4 and earlier don't seem to exhibit this bug.

UPDATE 2 (2016-03-17): Bug submitted to List-MoreUtils: https://rt.cpan.org/Ticket/Display.html?id=113117 . Thanks to everyone for the responses.

Replies are listed 'Best First'.
Re: List::MoreUtils' minmax bug?
by Anonymous Monk on Mar 16, 2016 at 16:26 UTC

    I can kinda sorta reproduce this, but this probably isn't the same as your problem:

    use warnings; use strict; use List::MoreUtils 'minmax'; my $envres = [ [], [], [{time=>0.002},{}] ]; my ($min, $max) = minmax(map {$_->{time}} @{$envres->[2]}); use Data::Dump 'dump'; dump $min, $max; __END__ Use of uninitialized value in subroutine entry at - line 5. Use of uninitialized value in subroutine entry at - line 5. (undef, 0.002)

    I smell a bug... what version of Perl and what version of List::MoreUtils?

    Also, are you feeding minmax plain scalars, or are they objects/tied vars/some other magical things? (See Devel::Peek)

      I'm on perl 5.22.0 and List::MoreUtils 0.413.

      Dumping $envres->[2][0]{time} with Devel::Peek reveals something:

      When there is no warning:

      SV = PVNV(0x279ae10) at 0x25ca100 REFCNT = 1 FLAGS = (POK,IsCOW,pPOK) IV = 0 NV = 0.00217268600128591 PV = 0x2895f60 "0.0022"\0 CUR = 6 LEN = 10 COW_REFCNT = 3

      When there is a warning:

      SV = PVNV(0x196bf40) at 0x170b110 REFCNT = 1 FLAGS = (POK,pPOK) IV = 0 NV = 0.002889619519036 PV = 0x1a6dbb0 "0.003"\0 CUR = 5 LEN = 10 Use of uninitialized value $max in numeric le (<=) at lib/Bencher/Form +atter/ScaleTime.pm line 30.

      Trying this with a few other perls: doesn't happen in 5.10.1, always seems to happen in 5.18.4, never seems to happen in 5.20.3 happens too in 5.20.3.

        Wow, Bencher::Backend has a lot of code, it's difficult to tell where $envres comes from. Unfortunately I think you'll need to boil it down to a code snippet that reproduces the problem (which still smells like a bug).

        I did notice that in one place $envres comes from JSON. Here's an interesting one...

        use warnings; use strict; use List::MoreUtils 'minmax'; use JSON::MaybeXS 'decode_json'; use Data::Dump 'dump'; my $envres = decode_json( q# [ [], [], [{"time":"0.0022"},{"time":false}] ] # ); dump $envres; my ($min, $max) = minmax(map {$_->{time}} @{$envres->[2]}); dump $min, $max; __END__ [ [], [], [ { time => 0.0022 }, { time => bless(do{\(my $o = 0)}, "JSON::PP::Boolean") }, ], ] Segmentation fault (core dumped)

      Reproduce what bug? Where is the bug?

      use List::MoreUtils 'minmax'; use Data::Dump 'dump'; dump minmax(0.002, undef);

Re: List::MoreUtils' minmax bug?
by toolic (Bishop) on Mar 16, 2016 at 16:02 UTC
    line 25 in ScaleTime.pm
    What is ScaleTime.pm? Can you provide a link to it if it's on CPAN? I can't see that file from either of your links.
      https://metacpan.org/source/PERLANCAR/Bencher-Backend-0.40/lib/Bencher/Formatter/ScaleTime.pm
Re: List::MoreUtils' minmax bug? (UPDATE 1)
by Anonymous Monk on Mar 16, 2016 at 20:13 UTC

    Apparently a more reliable test:

    $ perl -MList::MoreUtils=minmax -wMstrict -e ' my ($n,$x) = minmax(sprintf("%.4g",0.5)); die unless defined $x' Died at -e line 2.

    Discovered via this code (note the CUR in output):

    use warnings; use strict; use List::MoreUtils 'minmax'; use Devel::Peek; for (1..1000) { my $in = rand(); Dump(sprintf("%.4g",$in)); my ($min,$max) = minmax(sprintf("%.4g",$in)); last unless defined $max; }