如何有效地将小数点N位置向左移动?

时间:2016-07-04 20:05:31

标签: string perl decimal

我有一堆十进制数字(作为字符串),我从API收到。我需要' unscale'它们,即将它们除以10的幂。对于整数来说,这似乎是一个简单的task,但是我的小数没有保证范围。所以,基本上我需要一个像这样工作的函数:

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

我宁愿不使用浮动来避免任何舍入错误。

2 个答案:

答案 0 :(得分:7)

这有点冗长,但应该这样做:

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;
}

演示:

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

答案 1 :(得分:1)

除非您的数据包含的数字明显多于您显示的数字,否则浮点值的精确度足以达到您的目的。 Perl可以可靠地再现最多16位数值

cusparseDestroyMatDescr

输出

use strict;
use warnings 'all';
use feature 'say';

say move_point("12.34", 1); # "1.234"
say move_point("12.34", 5); # "0.0001234"
say move_point("1234", 12);
say move_point("123400", -9);

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

    my $dp = $v =~ /\.([^.]*)\z/ ? length $1 : 0;
    $dp += $n;
    $v /= 10**$n;

    sprintf '%.*f', $dp < 0 ? 0 : $dp, $v;
}


更新

如果标准浮点数的限制对您来说实际上是不充分的,那么核心Math::BigFloat将满足您的需求

此程序显示精确度为16位的数字,乘以10E-20到10E20的所有内容

1.234
0.0001234
0.000000001234
123400000000000

输出

use strict;
use warnings 'all';
use feature 'say';

use Math::BigFloat;

for ( -20 .. 20 ) {
    say move_point('1234567890.1234567890', $_);
}

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

    $v = Math::BigFloat->new($v);

    # Build 10**$n
    my $mul = Math::BigFloat->new(10)->bpow($n);

    # Count new decimal places
    my $dp = $v =~ /\.([^.]*)\z/ ? length $1 : 0;
    $dp += $n;

    $v->bdiv($mul);
    $v->bfround(-$dp) if $dp >= 0;
    $v->bstr;
}