検索

キーワード


【Java】浮動小数点の誤差はなぜ発生するの? その原因と対処方法を解説!

  • 公開日:2020-08-07 16:58:43
  • 最終更新日:2021-01-25 17:42:03
【Java】浮動小数点の誤差はなぜ発生するの? その原因と対処方法を解説!

こんにちは、駆け出しプログラマーの松倉です!

ここでは、浮動小数点の誤差の原因とその対処方法について紹介・解説します。

プログラミング未経験の方、Javaについて勉強している方、これから勉強したいと思っている方の参考になれば幸いです!

関連記事リンク:【Java】カンマ区切り?ゼロ埋め? 数値をフォーマット変換する方法!【Java】基本的な数値処理!Mathクラスの使い方を解説!【Java】演算子(算術演算子と複合演算子)の使い方を解説! インクリメントとデクリメントの使い方!


浮動小数点の誤差の原因と対処方法

javaではすべての計算処理は内部的に 2進数で表現されています。

2進数で表現した場合に無限小数となる数の演算は全て、浮動小数点に誤差が発生する可能性があります。

基本データ型は桁数が有限であるため、収まらない部分は四捨五入して近似値で表現されます

厳密な計算が求められる場合、この誤差は積み重なるほど大きな相違が起こり問題となります。

この記事では浮動小数点の誤差とその対策法など以下について解説します。


・基本データ型の有効範囲は?

・浮動小数点の誤差とは?

・浮動小数点の誤差の対処 BigDecimalクラス

浮動小数点の誤差の対処方法 BigDecimalによる演算

・浮動小数点の誤差の対処方法 BigIntegerクラス


基本データ型の有効範囲は?

整数を扱うデータ型はいくつかありますが、型毎に扱えるデータの種類や範囲が決められています。

基本データ型には整数や浮動小数点数、文字、論理型が用意されています。

javaでは計算を効率よく行えるようにするため、扱える値を一定の範囲までにしか使えないようになっています。

Javaで用意されている基本データ型とその有効範囲は以下になります。

               Javaで用意されている基本データ型とその有効範囲  



浮動小数点の誤差の原因とは?

丸め誤差とは、コンピュータが数値を 2進法を用いて限られた桁数で表現することにより発生する誤差です。

2進数で表現しようとすると循環小数になり近似値を使わざるを得ない場合に生じます。

浮動小数点型(float型や double型)の変数を使用する際に、型の桁数は有限であるため、無限小数は必ず丸め込みがされることがあります。

例えば 10進数の 0.1を 2進数で表すと 0.00011001100110011…となり循環小数なります。

これが、浮動小数点で誤差が発生する原因です。


0.8 + 0.9の計算を行ったサンプルコードを確認しましょう。

public class Sample1 {
	public static void main(String[] args) {
		double i1 = 0.8;
		double i2 = 0.9;

		System.out.println(i1 + i2);
	}
}


実行結果:

1.7000000000000002

計算すると「0.8 + 0.9 = 1.7」と算出されるはずですが、double型で算術演算子を使った場合誤差が生じて正確な値が算出できていません。

浮動小数点で誤差が発生する原因について、理解できたでしょうか?

小数点以下の誤差が許されない場合には浮動小数点型を使用しないように気をつけましょう。



浮動小数点の誤差の対処 BigDecimalクラス

誤差なく少数を扱うためには、BigDecimal クラスを使用します。

BigDecimalクラスは Javaで少数を扱う場合、丸め誤差が発生したときの小数点以下を正確に扱う為、または誤差についての処理方法を厳密にするために用意されています。

引数には浮動小数点数型と文字列型どちらも指定することができますが、浮動小数点数型を指定した場合、誤差が発生するので注意が必要になります。

正確な値を扱う場合、BigDecimalのコンストラクタの引数は文字列型で指定しましょう。


BigDecimalクラスは java.mathパッケージに属しており、java.lang.Mathクラスが提供するメソッドと同等のものを提供しています。

BigDecimalクラスを使用するには、「java.math.BigDecimal;」をインポートします。


BigDecimal型の使い方

BigDecimal 変数名 = new BigDecimal("数値");

※数値の部分はダブルクオーテーション(" ")で閉じる必要があります。

(" ")で閉じていない場合、引数の数値が int型や double型などに認識され誤差が生じます。

 

BigDecimalクラスのコンストラクタの引数を double型と String型で指定した場合のサンプルコードを確認しましょう。

import java.math.BigDecimal;

public class Sample2 {
    public static void main(String[] args) {
        BigDecimal i1 = new BigDecimal(0.6); //double型で指定
        BigDecimal i2 = new BigDecimal("0.6"); //String型で指定   

        System.out.println(i1);
        System.out.println(i2);
    }
}


実行結果:

0.59999999999999997779553950749686919152736663818359375
0.6 

BigDecimalクラスで double型を指定した場合、実行結果をみると誤差が発生しているのがわかります。

一方で String型を指定した場合、指定した通りの値が出力されています。

※BigDecimalクラスで正確な計算ができるのであれば、全て BigDecimalを使えば良いと思いがちですが、これは仕様によって判断しなければなりません。


個数や身長の平均値のような小数何十桁の精度がいらない表面上人間が感知できない場合、この誤差は本質的に無視できます

そのため演算すべてに BigDecimalクラスの丸めを指定することでコードが複雑になったり、演算処理速度が遅くなるデメリットも発生することを認識しておきましょう。



浮動小数点の誤差の対処方法 BigDecimalによる演算

BigDecimalクラスには、四則演算等の計算用のメソッドが用意されています。

BigDecimalを使った四則演算については以下のようになります。

・足し算 add()

・引き算 subtract()

・掛け算 multiply()

・割り算 divide()

 

各メソッドを使った演算のサンプルコードを確認しましょう。

import java.math.BigDecimal;

public class Sample3 {
	public static void main(String[] args) {
		BigDecimal i1 = new BigDecimal("5.0");
		BigDecimal i2 = new BigDecimal("10.0");
		BigDecimal i3 = new BigDecimal("3.0");

		BigDecimal add = i1.add(i2); //足し算
		BigDecimal sub = i1.subtract(i2); //引き算
		BigDecimal mul = i1.multiply(i3); // 掛け算
		BigDecimal div = i2.divide(i3, 2, BigDecimal.ROUND_HALF_UP); //割り算(少数第3位で四捨五入)

		System.out.println("足し算: " + add);
		System.out.println("引き算: " + sub);
		System.out.println("掛け算: " + mul);
		System.out.println("割り算: " + div);
	}
}


実行結果:

足し算: 15.0
引き算: -5.0
掛け算: 15.00
割り算: 3.33

※割り算の場合、下記のように丸め処理を記述せずに割り切れない計算をすると無限小数となり、ArithmeticExceptionの例外が発生します。

そのため実行結果の値を少数第 3位で四捨五入を行っています。



浮動小数点の誤差の対処方法 BigIntegerクラス

BigIntegerクラスは Javaで数学的な処理をする際に、扱う数値が基本データ型では扱えない程大きい場合に利用します。

long型の最大値は 9223372036854775807ですが、例えば 20桁(一垓)のような値は大きすぎて格納できる型がありません。

BigIntegerクラスに情報を格納するためのメモリは動的に割り当てられるため、論理的には BigIntegerクラスに格納できる値に上限はありません。


BigIntegerクラスは java.mathパッケージに属しており、java.lang.Mathクラスが提供するメソッドと同等のものを提供しています。

BigIntegerクラスを使用するには、「java.math.BigInteger;」をインポートします。

BigIntegerクラスを使った四則演算については以下のようになります。

・足し算 add()

・引き算 subtract()

・掛け算 multiply()

・割り算 divide()


また、基本データ型への変換メソッドも提供されています。

intValue()メソッドは BigIntegerクラスの値を int型へした変換値を返却することもできます。

intに変換する際に桁落ちなどで失われた情報がないかどうかを確認する場合、intValueExact()メソッドを使用します。

int型の範囲から外れている場合は、ArithmeticExceptionを返却します。

 

有効範囲外の値同士の掛け算をサンプルコードで確認しましょう。

import java.math.BigInteger;

public class Sample4 {
	public static void main(String[] args) {
		BigInteger i1 = new BigInteger("214748364744444444444444444444444444");
		BigInteger i2 = new BigInteger("-214748364744444444444444444444444444");

		System.out.println(i1.multiply(i2));
	}
}


実行結果:

-46116860160412949620864197530864197339976762202469135802469135802469136

BigIntegerクラスを用いることで int型、long型の有効範囲外の値が出力されています。


まとめ

浮動小数点で誤差が発生する原因と、その対処方法として BigDecimalクラス、BigIntegerクラスの使い方を解説しました。

数値の小数点以下を正確に扱う上でぜひ理解しておきましょう。

float型や double型では、演算処理の過程で誤差が積み重なり、正確な値が出力されない場合もあります。

金額など厳密な計算が必要な場合などは BigDecimalクラスを使って計算するようにしましょう。

また基本データ型の有効範囲外の値を用いる場合、BigIntegerクラスを使いましょう。

普通の計算方法とは違うので慣れが必要かもしれませんが、覚えておいて損はありません!

しっかりと学習しましょう!

 

関連記事リンク:【Java】カンマ区切り?ゼロ埋め? 数値をフォーマット変換する方法!【Java】基本的な数値処理!Mathクラスの使い方を解説!【Java】演算子(算術演算子と複合演算子)の使い方を解説! インクリメントとデクリメントの使い方!



【著者】

松倉祥大

はじめまして。フォワードソフト株式会社の松倉です。
システムエンジニアとして働き始めたのが2020年4月です。経験や知識がない状態で入社した私は、フォワードソフトの教育研修で一からプログラミングの勉強をしました。教育研修を卒業後、Javaのプログラミングについて初学者向けの記事を共同で制作しています。
知識や経験はまだまだですが、これからいろんな職場で様々な経験しながら勉強していきたいと思っています。

よく読まれている記事
【Java】JSPでタグライブラリを使う(JSTL)

【Java】JSPでタグライブラリを使う(JSTL)

こんにちは。エンジニアの新田です!ここでは、システムエンジニアとして働いている私が、システム開発手法や開発言語について紹介していこうと思います。今回は、JSPの標準タグライブラリ「JSTL」について紹介します。Javaについて勉強している方、Webアプリケーションを構築したいと思っている方の参考になれば幸いです!関連記事リンク: 【Java】JSPの基本的な構文/【Java】JSPのアクションタグ

【Java】Stringクラス文字列を操作するメソッドの使い方まとめ!実例も紹介!

【Java】Stringクラス文字列を操作するメソッドの使い方まとめ!実例も紹介!

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者✅にも、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!そもそもStringとは何?『 String 』... Java言語において文字列のデータ型を指します。基本デ

【Java】文字列の置き換え(String#format)!エスケープシーケンスのまとめも!!

【Java】文字列の置き換え(String#format)!エスケープシーケンスのまとめも!!

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者✅にも、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!Stringクラスformatメソッドの文字列整形【java.utilパッケージ】Formatterクラスfo

【Java】文字列格納後に変更可能!?StringBufferクラスとStringBuilderクラス!

【Java】文字列格納後に変更可能!?StringBufferクラスとStringBuilderクラス!

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者にも✅、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!文字列を扱う3つのクラス【java.langパッケージ】java.langパッケージの文字列を扱うクラスにはS

【Java】値?変数?型??しっかり解説!『データ型(プリミティブ型と参照型)』

【Java】値?変数?型??しっかり解説!『データ型(プリミティブ型と参照型)』

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者✅にも、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!プリミティブ型と参照型プログラム開発では型を持った変数を使ってデータのやり取りをしますが、データ型によって仕様