0
0

More than 3 years have passed since last update.

【ABC169(C問題)】PHPで浮動小数点数の丸め誤差を回避する

Last updated at Posted at 2020-06-16

はじめに

友人の影響で競技プログラミングに参加しています。
全然解けないのですが、問題が素晴らしいので、自分のような初心者にとっても勉強になることが多いなと感じています。
最近全然C問題が解けず・・・(白目)、169回目のC問題の復習をしたので載せます。

問題

問題はこちら
大体の内容:A(整数)*B(少数)の小数点以下を切り捨てて表示する

簡単簡単!と↓で出してWA(不正解)でした

<?php
    //入力読み込み
    fscanf(STDIN, "%d %f", $a,$b);
    //A×B
    $n = $a * $b;
    //切り捨て
    $n = floor($n);
    //文字で出ないようにする(気休め)
    printf("%.0F\n", $n);

?>

0.1 や 0.7 は二進数の二進数の浮動小数点数として正確に表現できない

PHPマニュアルサイト 浮動小数点数より
浮動少数点数の精度は有限なので、少数のまま計算を行い、後から小数点以下を切り捨てると誤差が出ます。

解説を読んで

解説で100倍にして整数に直してから積を計算して、最後に100で割ることで誤差なく計算できるとありましたがいまいちうまくいかず。。。
以下通らなかった処理↓

<?php
    fscanf(STDIN, "%d %s", $a, $b);
    $b = str_replace('.', '', $b);
    $n = floor(($a * $b) /100);
    printf("%.0F\n", $n);
?>

追記:コメントの方で、一度でも浮動小数点数になると誤差が出てしまうため、以下のように書くと良いというご指摘をいただきました。


<?php
    fscanf(STDIN, "%d %f", $a, $b);
    echo intdiv($a * intval($b * 100 + 0.5), 100);
?>

通った処理

他の方の回答を参考にして、以下通った処理です

1.intdiv()を使うパターン

(%では余りの方を出しますが、その逆に割れた方の数を出すやつです。)

<?php
    fscanf(STDIN, "%d %s", $a, $b);
    $b = str_replace('.', '', $b);
    echo intdiv($a * $b, 100);  
?>
2.bcmul()を使うパターン

更に、bcmul()という今回の問題のためにあるような関数がありました
( 2つの任意精度数値の乗算を行う、3つ目のパラメータに小数点以下の桁数を指定しない場合は小数点以下を切り捨てて表示する)

<?php
    fscanf(STDIN, "%d %f", $a, $b);
    echo bcmul($a,$b);
?>

なんてシンプルなの・・・泣

おわりに

一応浮動小数点数についても調べたのですが、全然理解できず・・・
ひとまず今回は

  • 少数の計算は誤差が出る可能性があるので気を付ける
  • intdiv()
  • bcmul()

の3つについて勉強できました。

復習は大切。。。。

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