数値

■ 数値を用いる

◆ いろいろな数値表現

Perl では、整数、小数、指数、8進数、16進数などの数値を使うことができます。

$a1 = 12345;     # 整数
$a2 = -12345;    # 負の数
$a3 = 123.45;    # 小数
$a4 = 123E45;    # 指数(123×10の45乗)
$a5 = 123e45;    # 指数(123×10の45乗)
$a6 = 0777;      # 8進数(0777 は 10進数の511)
$a7 = 0x7f;      # 16進数(0x7f は 10進数の127)
$a8 = 1_234_567; # $a8 = 1234567; と同じ

指数の E は大文字でも小文字でも構いません。0 で始まる数値は 8進数、0x で始まる数値は 16進数と解釈されます。数値中のアンダーバー(_)は無視されます。カンマ(,)の代用として 3桁毎に _ を入れることで、数値の桁を読みやすくするのに役立ちます。

◆ 8進数とは

0~7 の 8個の数字を用いて表すのが 8進数 です。0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 16, 17, 20, ... のように増えていきます。8進数の 123 は、1×8×8+2×8+3=83 で、10進数の 83 に相当します。(→ 8進数を扱う

◆ 16進数とは

16個の数字を用いて表すのが 16進数 です。16個の数字には 0~9 に加えて a~f の文字を使用します。0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f, 10, 11, ... のように増えていきます。16進数の 123 は、1×16×16+2×16+3=291 で、10進数の 291 に相当します。(→ 16進数を扱う

■ 切り捨てる(int)

◆ 切り捨てる

小数部を切捨てるには int() を用います。int には「整数(Integer)にする」という意味があります。

round1.pl
$a1 = 1234.56;
$a2 = int($a1);
print "a1=$a1, a2=$a2\n";

実行結果は次のようになります。

a1=1234.56, a2=1234

引数を省略すると、省略時変数 $_ を対象とします。

$n = int($a);
$n = int();             # int($_) と同じ意味

■ 100の位で切り捨てる

◆ 小数部以外の桁で切り捨て

数値を 100 の位で切り捨てるには、数値を一度 1000 で割り、それを int() で整数化した後に 1000 倍します。

round2.pl
$a1 = 1234.56;
$a2 = int($a1 / 1000) * 1000;
print "a1=$a1, a2=$a2\n";

実行結果は次のようになります。

a1=1234.56, a2=1000

同様に、0.01 の位で切り捨てるには、数値を 10 倍して int() したものを 10 で割ります。

$a1 = 1234.56;
$a2 = int($a1 * 10) / 10;
print "a1=$a1, a2=$a2\n";

■ 切り上げる

◆ 小数部の切り上げ

小数以下を切り上げるには、小数部を切り捨てた値に 1 を足します。ただし、小数部が 0 の時には 1 を足してはならないので、ちょっと処理は複雑になります。下記のスクリプトでは、int($a1) が $a1 に等しければそのまま $a1 を、さもなくば int($a1) に 1 を足した値を $a2 に代入しています。

reval.pl
$a1 = 1234.56;
$a2 = (int($a1) == $a1) ? $a1 : int($a1) + 1;
print "a1=$a1, a2=$a2\n";

実行結果は次のようになります。

a1=1234.56, a2=1235

「式 ? 値1 : 値2」の使用法については 条件演算子 を参照してください。

■ 四捨五入する

◆ 四捨五入

小数第1位で四捨五入するには、0.5 を足して小数部を切り捨てます。

roundoff.pl
$a1 = 1234.56;
$a2 = int($a1 + 0.5);                # 小数第1位で四捨五入する
print "a1=$a1, a2=$a2\n";

実行結果は次のようになります。

a1=1234.56, a2=1235

100 の位で四捨五入するには、1000 で割って 0.5 を足した後、小数部を切り捨てて、1000 倍します。

$a1 = 1234.56;
$a2 = int($a1 / 1000 + 0.5) * 1000;  # 100の位で四捨五入する
print "a1=$a1, a2=$a2\n";

■ ランダムな数値を得る(srand, rand)

◆ 乱数を得る

rand(n) は、0 以上 n 未満の乱数を返します。rand() で求めた値を int() で整数化することにより、0 以上 n 未満の整数値を得ることができます。

rand.pl
for ($i = 0; $i < 3; $i++) {
   $xx = int(rand(100));
   print "xx=$xx\n";
}

実行結果の例を以下に示します。

xx=25
xx=46
xx=53
◆ srand()の必要性

Perl 5.004 よりも古いバージョンでは、rand() を用いるプログラムの先頭で 1度だけ srand() を呼び出す必要があります。srand() を呼び出さない場合、プログラムはいつも同じ数字から始まって、同じ順序の乱数を生成してしまいます。

srand();
for ($i = 0; $i < 3; $i++) {
   $xx = int(rand(100));
   print "xx=$xx\n";
}

srand(num) の num には乱数の初期値の元となる数値を指定します。num が同じだと、同じ順序の乱数が生成されてしまいます。省略時は time() の値が使用されるので起動した時刻により別の順序の乱数が生成されますが、悪意のあるハッカーには、time() の値を予測して乱数の結果も予測されてしまいますので、ちょっと危険です。UNIX でしか使用できませんが、プロセス番号($$)を用いて例えば次のようにするとかなり安全になります。

srand(time() ^ ($$ + ($$ << 15)));

■ 3桁ずつカンマで区切る

◆ 3桁区切り

12,345,678.9999 のように数値を 3桁区切りで表示にしたい場合、下記のようにします。

commify.pl
$num = 12345678.9999;
while ($num =~ s/^([-+]?\d+)(\d{3})/$1,$2/) { }
print "$num\n";

実行結果は次のようになります。

12,345,678.9999

何をやっているのかよくわからないマニアックなスクリプトですが、12345678 を 12345,678 に置換、12345 を 12,345 に置換、という処理を、置換するものが無くなるまで繰り返しています。詳細は 正規表現置換 を参照してください。

■ 桁区切りされた数値を解釈する

◆ 桁区切りされた数値を解釈する

桁区切りされた数値を解釈するには、置換のテクニックですべてのカンマ(,)を取り除きます。

$num = "12,345,678.9999";
$num =~ s/,//g;
print "$num\n";

コラム

日本や米国では小数点にピリオド(.)、桁区切りにカンマ(,)を用いますが、ドイツなどでは逆になります。1,000 は 1、1.000 は 1000 を意味します。それぞれの国でそれぞれの文化に応じた数値表現を持っている訳ですから、日本もカンマは 3桁区切りではなく、4桁区切りにしてもよかったのかもしれませんね。

■ 16進数を扱う(hex)

◆ 16進数と10進数の変換

10進数の数値を 16進数の文字列に変換するには sprintf() の %x を用います。逆に、16進数の文字列を 10進数の数値に変換するには hex() を用います。数値同士であれば特に変換は不要です。

hex.pl
# 10進数(数値)から16進数(文字列)への変換
$hex = sprintf("%02x", 255);        # $hex = "ff"
print "hex = $hex\n";      

# 16進数(文字列)から10進数(数値)への変換
$dec = hex("ff");                   # $dec = 255
print "dec = $dec\n";

# 16進数(数値)から10進数(数値)への変換
$dec = 0xff;                        # $dec = 255
print "dec = $dec\n";      

実行結果は次のようになります。

hex = ff
dec = 255
dec = 255
補足

コンピュータ内部では数値を 2進数で表現しますので、10進数に変換というのは正確な表現ではありません。変数に代入した時点で 2進数で記憶され、print "..." で表示する際に 2進数→10進数の変換が行われます。

■ 8進数を扱う(oct)

◆ 8進数と16進数の変換

10進数の数値を 8進数の文字列に変換するには sprintf() の %o を用います。逆に、8進数の文字列を 10進数の数値に変換するには oct() を用います。数値同士であれば特に変換は不要です。

oct.pl
# 10進数(数値)から8進数(文字列)への変換
$oct = sprintf("%03o", 255);        # $oct = "377"
print "oct = $oct\n";

# 8進数(文字列)から10進数(数値)への変換
$dec = oct("377");                  # $dec = 255
print "dec = $dec\n";

# 8進数(数値)から10進数(数値)への変換
$dec = 0377;                        # $dec = 255
print "dec = $dec\n";

実行結果は次のようになります。

oct = 377
dec = 255
dec = 255

■ 2進数を扱う

◆ 10進数から2進数(文字列)への変換(%b)

10進数の数値を 2進数の文字列に変換するには sprintf() の %b を用います。ただし、古い perl のバージョンではサポートしていません。

# 10進数(数値)から2進数(文字列)への変換
$bin = sprintf("%08b", 255);        # $bin = "11111111"
print "bin = $bin\n";
◆ 10進数から2進数(文字列)への変換(サブルーチン)

printf() で %b を使用できない場合は、下記のようなサブルーチンを用いると便利です。第2引数には桁数を指定します。

numtobin.pl
print NumToBin(0x5555, 16) . "\n";

# 数値から2進数文字列への変換
# NumToBin(数値, 桁数)
sub NumToBin {
    local($num, $fig) = @_;
    local($bin);
    do {
        $bin = ($num % 2) . $bin;
    } while ($num = int($num / 2));
    return "0" x ($fig - length($bin)) . $bin;
}
◆ 2進数(文字列)から10進数への変換

hex() や oct() と同様に bin() があればよいのですが、現在の Perl には実装されていません。仕方ないので、下記のようなサブルーチンを用います。

bintonum.pl
printf("%x\n", BinToNum("0101010101010101"));

# 2進数文字列から数値への変換
# BinToNum(2進数文字列)
sub BinToNum {
    local($bin) = @_;
    local($dec);
    $bin =~ s/(.)/{ $dec *= 2; if ($1) { $dec += 1; } }/eg;
    return $dec;
}

■ 数学演算を行う(sqrt, sin, cos, atan2, exp, log, abs)

◆ 各種数学演算

sin() や cos() など、いくつかの数学的演算用の関数が定義されています。

$pi = 3.141592653589793;     # 円周率
$r = 100;                    # 半径
$a = 30;                     # 角度 30°

$x = $r * cos($pi * $a / 180);    # X座標を求める
$y = $r * sin($pi * $a / 180);    # Y座標を求める
$a = atan2($y, $x) / $pi * 180;   # 座標 X, Y の角度を求める

printf("x=%5.2f, y=%5.2f, a=%d\n", $x, $y, $a);

これを実行すると次のようになります。

x=86.60, y=50.00, a=29
◆ 数学演算関数一覧

数学関数には次のようなものがサポートされています。

関数説明
sqrt(x)x の平方根。
sin(x)sin x。x は180度をπとするラジアン単位で指定する。
cos(x)cos x。x は180度をπとするラジアン単位で指定する。
atan2(y, x)atan y/x。座標から角度を求める際に使用される。
exp(n)e の n 乗。
log(x)e を基とする x の log。
abs(x)x の絶対値。(Perl5~)

■ 丸め誤差を考慮する

◆ 丸め誤差とは

コンピュータ内部では小数を2進数で保持しています。0.5 であれば 1/2 のように正確に表現することができるのですが、0.1 は 1/16 + 1/32 + 1/256 + 1/512 + ... のような近似値として表現します。この際、0.1 は正確な 0.1 ではないといった、丸め誤差 の問題が生じます。例えば、

gosa1.pl
$num = 0.0;
for ($i = 1; $i <= 20; $i++) {
    $num += 0.1;
    if ($num * 10 != $i) {
        print "$num × 10 は $i ではありません。\n";
    }
}

を実行すると 0.3 が正確な 0.3 ではなくなるために、例えば次のように表示されてしまいます。

0.3 × 10 は 3 ではありません。
0.8 × 10 は 8 ではありません。
1 × 10 は 10 ではありません。
   :
◆ 丸め誤差の影響を少なくする

丸め誤差の影響を少なくするには、誤差が蓄積してしまう累計処理をかけ算に置きかえたり、有効桁で四捨五入するなどの改善を行います。

gosa2.pl
$num = 0.0;
for ($i = 1; $i <= 20; $i++) {
    $num = 0.1 * $i;                       # 累計をかけ算に変更
    $num = int(($num * 10) + 0.5) / 10;    # 有効桁で四捨五入
    if ($num * 10 != $i) {
        print "$num × 10 は $i ではありません。\n";
    }
}

■ 巨大な桁数の整数を扱う(BigInt)

◆ 巨大整数を扱う

10桁を超える巨大な整数を扱うと 1e+015 などの指数付きで表現されてしまったり、丸め誤差の問題が発生したりします。Perl で 10桁を超える整数を扱う場合は BigInt モジュールを使用すると便利です。

bigint.pl
use Math::BigInt;

$n1 = Math::BigInt->new("123456789012345678901234567890");
$n2 = $n1 * 10000;
$n3 = $n2 + 1;
printf("%s\n", $n3);

結果は次のようになります。

+1234567890123456789012345678900001
◆ BigInt 使用上の注意

new(...) の ... 部分で値を渡すときや、値を参照する時は数値ではなく、文字列として扱ってください。下記の例では値を数値で渡しているために結果が NaN となってしまいます。

use Math::BigInt;

$n1 = Math::BigInt->new(123456789012345678901234567890);
print "$n1\n";
◆ BigInt の演算子

BigInt は Perl の通常の整数ではありませんが、+ や <=> などの演算子がオーバーライドされているため、通常の整数のように数値演算子を用いることができます。文字列として比較することも可能です。また、badd()、bsub()、bnum()、bdiv()、bcmp() などの演算メソッドも用意されています。

use Math::BigInt;

$n1 = Math::BigInt->new("123456789012345678901234567890");
$n2 = $n1 + 1;

if ($n1 == $n2) { print "等しい\n"; }
if ($n1 eq $n2) { print "等しい\n"; }
if ($n1->bcmp($n2) == 0) { print "等しい\n"; }

Copyright (C) 2002 杜甫々