0

I have a PDL (type double) of mixed values (both positive and negative). I want to round each entry towards zero. So +1.2 becomes +1, +1.7 becomes +1, -1.2 becomes -1, and -1.7 becomes -1,

I thought of using int(), but it doesn't work on PDL types.

I can also use round(abs($x) - 0.5) * ($x <=> 0), but not sure how to use this logic on a PDL.

Pointers?

CinCout
  • 9,486
  • 12
  • 49
  • 67

2 Answers2

4

The documentation of the rint function in PDL::Math says:

If you want to round half-integers away from zero, try floor(abs($x)+0.5)*($x<=>0).

Just change it slightly to make it work the way you want:

#!/usr/bin/perl
use warnings;
use strict;

use PDL;

my $pdl = 'PDL'->new(
    [  1,  1.3,  1.9,  2,  2.1,  2.7 ],
    [ -1, -1.3, -1.9, -2, -2.1, -2.7 ]
);
$pdl = floor(abs($pdl)) * ($pdl <=> 0);
print $pdl;

Output:

[
 [ 1  1  1  2  2  2]
 [-1 -1 -1 -2 -2 -2]
]
choroba
  • 231,213
  • 25
  • 204
  • 289
1

PDL::Math has floor, ceil, and rint. All these functions work in place.

Therefore, something like the following should work:

#!/usr/bin/env perl
use warnings;
use strict;

use PDL;

my $pdl = 'PDL'->new(
    [  1,  1.3,  1.9,  2,  2.1,  2.7 ],
    [ -1, -1.3, -1.9, -2, -2.1, -2.7 ]
);

print $pdl;

floor(inplace $pdl->where($pdl >= 0));
ceil (inplace $pdl->where($pdl <  0));

print $pdl;

Output:

[
 [   1  1.3  1.9    2  2.1  2.7]
 [  -1 -1.3 -1.9   -2 -2.1 -2.7]
]

[
 [ 1  1  1  2  2  2]
 [-1 -1 -1 -2 -2 -2]
]

PS: @choroba's answer seems to run about 20% faster in the following benchmark with non-threaded perl 5.24 on an ancient MacBook Pro:

#!/usr/bin/env perl
use warnings;
use strict;

use constant N_ELEMS => $ARGV[0] || 100_000;

use Dumbbench;
use PDL;

sub one_scan {
    my $pdl = 100 * grandom(N_ELEMS);
    $pdl = floor(abs($pdl)) * ($pdl <=> 0);
    return;
}

sub two_scans {
    my $pdl = 100 * grandom(N_ELEMS);
    floor(inplace $pdl->where($pdl >= 0));
    ceil (inplace $pdl->where($pdl <  0));
    return;
}

sub baseline {
    my $pdl = 100 * grandom(N_ELEMS);
    return;
}

my $bench = Dumbbench->new;

$bench->add_instances(
    Dumbbench::Instance::PerlSub->new(code => \&baseline,  name => 'Baseline'),
    Dumbbench::Instance::PerlSub->new(code => \&one_scan,  name => 'One Scan'),
    Dumbbench::Instance::PerlSub->new(code => \&two_scans, name => 'Two Scans'),
);

$bench->run;
$bench->report;
Sinan Ünür
  • 116,958
  • 15
  • 196
  • 339
  • I am looking to round the values towards zero, not away from it. Also, if my pdl variable is `$myPDL`, how will I use `abs()` and `<=>`? Will the `$x` become `$myPdl` instead, with the function being applied to each entry in the PDL automatically? – CinCout Jun 20 '17 at 11:24
  • In the condition `($x >= 0)`, `$x` is an individual variable. How do I make this check for each entry in the PDL and then go for either `floor()` or `ceil()`? – CinCout Jun 20 '17 at 11:29
  • Should the statement be `($myPdl >= 0) ? floor($myPdl) : ceil($myPdl);` and it will do it for all the entries? – CinCout Jun 20 '17 at 11:37
  • @choroba `floor()` will not round *towards* zero. Will fail for, say, `-12.2` – CinCout Jun 20 '17 at 11:42
  • @CinCout: That's why there's `abs`. See my reply. – choroba Jun 20 '17 at 11:51