0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

11: 浮動小数点数の演算に注意, 代入演算子

Last updated at Posted at 2022-03-08

浮動小数点数の演算に注意

浮動小数点数を含んだ演算子は、時として意図した結果を得られない場合がある。

たとえば↓

<?php
print floor((0.1 + 0.7) * 10);
//結果: 7

floorは、少数点数を切り捨てるための関数。

この場合、(0.1 + 0.7) * 10で8なので、切り捨てても結果は8となるはずだが結果は7である。

これは浮動小数点数が、内部的に2進数で演算されるために発生する誤差。

10進数ではごく単純に表せる0.1という値ですら、2進数の世界では0.00011001100110011………という無限循環少数となる。

結果として、(0.1 + 0.7) * 10も、内部的には7.9999999999999991118……………のような値となり、正しい結果を得られない。

このような問題を避けるには、任意精度数学関数を利用する。

<?php
①→ $add = bcadd(0.1, 0.7, 1);
$mul = bcmul($add, 10, 1);

print floor($mul);  //結果: 8

任意精度関数では、bcxxxxxという命令でもって加減乗除、剰余、比較などの演算を実施する。

bcadd(加算)

bcsub(減算)

bcmul(乗算)

bcdiv(除算)

bccomp(比較)

のような関数がある。

いずれも先頭から「左オペランド」「右オペランド」「小数点以下の桁数」を渡す。

よって①であれば「0.1+0.7を小数点以下1桁まで計算しなさい」という意味になる。

関数の呼び出しとなった分、コードは冗長になるが、確かに誤差が解消されていることが確認できる。

配列の結合

加算演算子(+)は、配列の加算(結合)にも対応している。

plus_array.php↓

$assoc1 = [
   ‘Apple’  => ‘Red’,
   ‘Orange’ => ‘Yellow’,
   ‘Melon’  => ‘Green’
];
$assoc2 = [
   ‘Grape’       => ‘Purple’,
   ‘Apple’       => ‘Green’,
   ‘Strawberry’  => ‘Red’
];

$result = $assoc1 + $assoc2;
print_r($result);
////////////////ここまでが①//////////////

$ary1 = [1, 3, 5];
$ary2 = [2, 3, 6];
$result = $ary1 + $ary2;

print_r($result);
////////////////ここまでが②/////////////

実行結果:

Array(
  [Apple]      => Red
  [Orange]     => Yellow
  [Melon]      => Green
  [Grape]      => Purple
  [Strawberry] => Red
 )

Array(
  [0] => 1
  [1] => 3
  [2] => 5
 )

ここで注意が必要なのは、加算演算子は、
左の配列に存在しないキーの要素を右の配列から取り出し、左の配列に追加する
ということ。

つまり、右の配列に左の配列と同じキーがあった場合、その値は無視される。

この考え方を念頭に先ほどの plus_array.phpの結果を見てみる。

まず①では、配列$assoc1と$assoc2とでキー’Apple’が重複しているので、$assoc2の「’Apple’ => ‘Green’」は無視される。

②では、そもそも配列$ary1も$ary2も数値キーとして0〜2を持った配列なので、配列$ary2の内容はすべて無視される。

特に②については、[1, 2, 3, 5, 6]のような結果を期待するかもしれないが、そうはならない。

代入演算子

左辺で指定した変数に対して、右辺の値をセット(代入)するための演算子。

何度も出てきた「=」演算子は、代表的な代入演算子の一つである。

また、代入演算子には、代数演算子やビット演算子などを合わせた機能を提供する複合代入演算子も含まれる。

主な代入演算子

演算子 概要 用例
= 変数などに値を代入 $x = 10;
+= 左辺と右辺を加算した結果を、左辺に代入 $x = 5; $x += 2; → 7
-= 左辺から右辺を減算した結果を、左辺に代入 $x = 5; $x -= 2; → 3
*= 左辺と右辺を乗算した結果を、左辺に代入 $x = 5; $x *= 2; → 10
**= 左辺を右辺で累乗した結果を、左辺に代入 $x = 5; $x **= 2; → 25
/= 左辺を右辺で除算した結果を、左辺に代入 $x = 5; $x /= 2; → 2.5
%= 左辺を右辺で除算した余りを、左辺に代入 $x = 5; $x %= 2; → 1
.= 左辺と右辺を連結した文字列を、左辺に代入 $x = ‘Foo’; $x .= ‘Bar’; → FooBar
??= 左辺が非nullならその値を、nullなら右辺の値を代入 $x = 10; $x ??= 1; → 10
&= 左辺と右辺をビット論理積した結果を、左辺に代入 $x = 10; $x &= 1; → 0
|= 左辺と右辺をビット論理和した結果を、左辺に代入 $x = 10; $x |= 1; → 11
^= 左辺と右辺をビット排他的論理和した結果を、左辺に代入 $x = 10; $x ^= 1; → 11
<<= 左辺を右辺の値だけ左シフトした結果を左辺に代入 $x = 10; $x <<= 1; → 20
>>= 左辺を右辺の値だけ右シフトした結果を左辺に代入 $x = 10; $x >>= 1; → 5

複合代入演算子は、「左辺と右辺の値を演算した結果を左辺に代入する」ための演算子。

つまり、次のコードは意味的に等価である。
(●は、複合演算子として利用できる任意の代数/ビット/文字列を表すものとする)。

$i ●= $j ↔︎ $i = $i ● $j;

代数/ビット/文字列演算した結果を元の変数に書き直したい場合には、複合代入演算子を利用することで、よりシンプルにコードを記述できる。

0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?