planetp planetp - 5 months ago 16
Perl Question

How to move the decimal point N places to the the left efficiently?

I have a bunch of decimal numbers (as strings) which I receive from an API. I need to 'unscale' them, i.e. divide them by some power of 10. This seems a simple task for integers, but I have decimals with no guaranteed range. So, basically I need a function that works like this:

move_point "12.34" 1; # "1.234"
move_point "12.34" 5; # "0.0001234"


I'd rather not use floats to avoid any rounding errors.

Answer

This is a bit verbose, but should do the trick:

sub move_point {
    my ($n, $places) = @_;

    die 'negative number of places' if $places < 0;
    return $n if $places == 0;

    my ($i, $f) = split /\./, $n;  # split to integer/fractional parts

    $places += length($f);

    $n = sprintf "%0*s", $places+1, $i.$f;  # left pad with enough zeroes
    substr($n, -$places, 0, '.');  # insert the decimal point
    return $n;
}

Demo:

my $n = "12.34";

for my $p (0..5) {
    printf "%d  %s\n", $p, move_point($n, $p);
} 

0  12.34
1  1.234
2  0.1234
3  0.01234
4  0.001234
5  0.0001234