- JAVA
Javaデータ型一覧|基本型8種の範囲・最大値とオーバーフロー
Javaの基本データ型(プリミティブ型)とは、数値・文字・真偽値をそのまま変数に入れる8種類の型で、byte・short・int・long・float・double・char・boolean のことです。 型ごとに使えるビット数が決まっているため、入れられる値の範囲(最小値と最大値)も決まっています。
範囲を超える計算をしても、Javaの整数演算はエラーを出さずに計算を続けます。これが「オーバーフロー(桁あふれ)」で、気づかないうちに値がマイナスになっていた、という場面につながります。この記事では、8種類すべての範囲・既定値・リテラルの書き方を1枚の表にまとめ、桁あふれ・小数の誤差・charの演算を実際に動かした結果と、それぞれの対策を示します。
確認環境:Ubuntu 24.04 / OpenJDK 21.0.10(javac 21.0.10)。コンパイルエラーの文面は javac の英語表示と日本語表示を併記しています。
Java基本データ型8種の早見表
| 型 | ビット数 | 最小値 | 最大値 | 既定値 | ラッパー | リテラルの書き方 |
|---|---|---|---|---|---|---|
byte |
8 | -128 | 127 | 0 | Byte |
byte b = 10;(専用の接尾辞なし) |
short |
16 | -32,768 | 32,767 | 0 | Short |
short s = 10;(専用の接尾辞なし) |
int |
32 | -2,147,483,648 | 2,147,483,647(約21億) | 0 | Integer |
100、1_000_000、0xFF、0b1010 |
long |
64 | -9,223,372,036,854,775,808 | 9,223,372,036,854,775,807(約922京) | 0 | Long |
末尾に L:3_000_000_000L |
float |
32 | -3.4028235E38 | 3.4028235E38(有効桁は約7桁) | 0.0 | Float |
末尾に f:3.14f |
double |
64 | -1.7976931348623157E308 | 1.7976931348623157E308(有効桁は約15〜16桁) | 0.0 | Double |
3.14、1.5e3(小数は何も付けなければ double) |
char |
16 | 0 | 65,535 | 文字コード 0 | Character |
シングルクォート:'A'、'あ' |
boolean |
- | false | true | false | Boolean |
true / false |
- 既定値はフィールドと配列の要素に入る値です。メソッドの中のローカル変数には既定値が入らず、代入する前に使うとコンパイルエラーになります(後述)。
float・doubleの「最小値」は、絶対値がもっとも大きい負の数です。定数Float.MIN_VALUE・Double.MIN_VALUEは「0 より大きい最小の数」を表すため、意味が異なります。- 範囲を定数で取り出すときは
Integer.MAX_VALUE・Long.MIN_VALUEのように、ラッパークラスの定数を使います。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Javaデータ型一覧|基本型8種の範囲・最大値とオーバーフロー」
範囲と既定値をプログラムで確かめる
早見表の値は、ラッパークラスの定数から出力できます。TypeTable.java として保存して実行してください。
public class TypeTable {
static byte b; static short s; static int i; static long l;
static float f; static double d; static char c; static boolean z;
public static void main(String[] args) {
System.out.printf("%-8s %4s %-24s %-24s %s%n", "型", "bit", "最小値", "最大値", "既定値");
System.out.printf("%-8s %4d %-24s %-24s %s%n", "byte", Byte.SIZE, Byte.MIN_VALUE, Byte.MAX_VALUE, b);
System.out.printf("%-8s %4d %-24s %-24s %s%n", "short", Short.SIZE, Short.MIN_VALUE, Short.MAX_VALUE, s);
System.out.printf("%-8s %4d %-24s %-24s %s%n", "int", Integer.SIZE, Integer.MIN_VALUE, Integer.MAX_VALUE, i);
System.out.printf("%-8s %4d %-24s %-24s %s%n", "long", Long.SIZE, Long.MIN_VALUE, Long.MAX_VALUE, l);
System.out.printf("%-8s %4d %-24s %-24s %s%n", "float", Float.SIZE, -Float.MAX_VALUE, Float.MAX_VALUE, f);
System.out.printf("%-8s %4d %-24s %-24s %s%n", "double", Double.SIZE, -Double.MAX_VALUE, Double.MAX_VALUE, d);
System.out.printf("%-8s %4d %-24s %-24s %s%n", "char", Character.SIZE,
(int) Character.MIN_VALUE, (int) Character.MAX_VALUE, "(int)" + (int) c);
System.out.printf("%-8s %4s %-24s %-24s %s%n", "boolean", "-", "false", "true", z);
System.out.println();
System.out.println("Float.MIN_VALUE (正の最小) = " + Float.MIN_VALUE);
System.out.println("Double.MIN_VALUE (正の最小) = " + Double.MIN_VALUE);
}
}
javac -d out TypeTable.java
java -cp out TypeTable
実行結果:
型 bit 最小値 最大値 既定値
byte 8 -128 127 0
short 16 -32768 32767 0
int 32 -2147483648 2147483647 0
long 64 -9223372036854775808 9223372036854775807 0
float 32 -3.4028235E38 3.4028235E38 0.0
double 64 -1.7976931348623157E308 1.7976931348623157E308 0.0
char 16 0 65535 (int)0
boolean - false true false
Float.MIN_VALUE (正の最小) = 1.4E-45
Double.MIN_VALUE (正の最小) = 4.9E-324
Java言語仕様(JLS 4.2.1)にも、byte は -128〜127、short は -32768〜32767、int は -2147483648〜2147483647、long は -9223372036854775808〜9223372036854775807、char は 0〜65535 と定められています。Javaでは、この範囲がOSやCPUによって変わることはありません。
int・long・doubleの違いと選び方
8種類ありますが、ふだん使うのは int・long・double・boolean の4つが中心です。
| 用途 | 選ぶ型 | 理由 |
|---|---|---|
| 件数、回数、添字、年齢など | int |
整数リテラルの基本の型。約21億まで入る |
| 21億を超え得る数(ID、ミリ秒、バイト数、累計) | long |
約922京まで入る |
| 平均、割合、座標、計測値など、わずかな誤差を許せる小数 | double |
小数リテラルの基本の型。有効桁は約15〜16桁 |
| 金額、税率など、10進数で正確に計算したい小数 | BigDecimal(基本型ではない) |
0.1 を正確に表せる |
| long でも足りない整数 | BigInteger(基本型ではない) |
桁数に上限がない |
| はい/いいえ | boolean |
true か false |
| 1文字 | char |
文字列は String を使う |
| 大量のバイト列(ファイル、通信データ) | byte の配列 |
1要素が8ビットで済む |
int と long の違いは入る範囲、long と double の違いは整数を正確に持つか、小数を近い値で持つかです。short と float は、メモリを節約したい大きな配列などで選ぶ型で、通常の変数では int と double を使えば十分です。
リテラルの書き方と、付け忘れで出るコンパイルエラー
リテラルとは、コードに直接書く値のことです。書き方によって型が決まります。
public class Literal {
public static void main(String[] args) {
int dec = 1_000_000; // 桁区切り
int hex = 0xFF; // 16進
int oct = 017; // 8進(先頭が 0)
int bin = 0b1010; // 2進
long big = 3_000_000_000L; // long は末尾に L
float f = 3.14f; // float は末尾に f
double d = 3.14; // 小数は何も付けなければ double
double e = 1.5e3; // 指数表記
char ch = 'A'; // 1文字はシングルクォート
char uni = 'あ'; // Unicode エスケープ(あ)
boolean ok = true;
var v1 = 10; var v2 = 10L; var v3 = 1.0; var v4 = 1.0f; var v5 = 'x';
System.out.println(dec + " " + hex + " " + oct + " " + bin + " " + big);
System.out.println(f + " " + d + " " + e + " " + ch + " " + uni + " " + ok);
System.out.println(((Object) v1).getClass().getSimpleName() + " " + ((Object) v2).getClass().getSimpleName()
+ " " + ((Object) v3).getClass().getSimpleName() + " " + ((Object) v4).getClass().getSimpleName()
+ " " + ((Object) v5).getClass().getSimpleName());
}
}
実行結果:
1000000 255 15 10 3000000000
3.14 3.14 1500.0 A あ true
Integer Long Double Float Character
3行目は、var で宣言した変数がどの型になったかを表示しています。接尾辞なしの整数は int、L 付きは long、接尾辞なしの小数は double、f 付きは float です。017 が 15 と表示されているとおり、先頭に 0 を付けた整数は8進数として読まれる点にも気をつけてください。
リテラルで出るコンパイルエラー
接尾辞の付け忘れなどで出るエラーを、実際にコンパイルして集めました。
public class LitErr {
public static void main(String[] args) {
int a = 3000000000; // int の範囲を超えるリテラル
long b = 3000000000; // 受ける側が long でも、リテラルに L が必要
}
}
LitErr.java:3: エラー: 整数が大きすぎます。
int a = 3000000000; // int の範囲を超えるリテラル
LitErr.java:4: エラー: 整数が大きすぎます。
long b = 3000000000; // 受ける側が long でも、リテラルに L が必要
英語表示では error: integer number too large です。受け取る変数が long でも、右辺のリテラルに L がなければ int として読まれるため、同じエラーになります。3000000000L と書けば通ります。
型が合わない代入のエラーも見ておきます。
public class CompileErr {
public static void main(String[] args) {
float f = 3.14; // 小数リテラルは double
int i = 3.5; // double を int へ
int j = 10L; // long を int へ
byte x = 10, y = 20;
byte z = x + y; // byte 同士の足し算は int になる
char c = 'a';
char next = c + 1; // char + int は int になる
byte big = 200; // byte の範囲外
boolean flag = 1; // 数値を boolean にはできない
}
}
| 行 | javac のエラー文(英語) | 日本語表示 | 直し方 |
|---|---|---|---|
float f = 3.14; |
incompatible types: possible lossy conversion from double to float |
不適合な型: 精度が失われる可能性があるdoubleからfloatへの変換 | 3.14f と書く |
int i = 3.5; |
possible lossy conversion from double to int |
同上(doubleからint) | (int) 3.5、または double で受ける |
int j = 10L; |
possible lossy conversion from long to int |
同上(longからint) | L を外す、または long で受ける |
byte z = x + y; |
possible lossy conversion from int to byte |
同上(intからbyte) | (byte) (x + y)、または int で受ける |
char next = c + 1; |
possible lossy conversion from int to char |
同上(intからchar) | (char) (c + 1) |
byte big = 200; |
possible lossy conversion from int to byte |
同上(intからbyte) | 127 以下にする、または int で受ける |
boolean flag = 1; |
incompatible types: int cannot be converted to boolean |
不適合な型: intをbooleanに変換できません | true / false を書く |
possible lossy conversion(精度が失われる可能性がある変換)は、大きい型から小さい型へ代入しようとしたときの目印です。Javaは、値が欠けるおそれのある代入を、キャストを書かない限り受け付けません。
演算結果の型
byte 同士の足し算が int になったように、演算には「結果の型」の決まりがあります。実行して確かめた結果を表にします。
| 式 | 結果の型 | 決まり |
|---|---|---|
short + byte |
int | int より小さい整数型は、演算の前に int になる |
'a' + 1 |
int | char も int になる |
1 + 2L |
long | long が混ざれば long |
1 + 2.0f |
float | float が混ざれば float |
1L + 2.0 |
double | double が混ざれば double |
既定値が入る場所と入らない場所
早見表の「既定値」が入るのは、フィールド(クラスに直接書いた変数)と配列の要素です。配列で確かめます。
int[] arr = new int[2]; double[] da = new double[1]; boolean[] ba = new boolean[1];
char[] ca = new char[1]; String[] sa = new String[1];
System.out.println(arr[0] + " " + da[0] + " " + ba[0] + " " + (int) ca[0] + " " + sa[0]);
0 0.0 false 0 null
基本型は 0・0.0・false、String のような参照型は null になります。JLS 4.12.5 でも、整数型は 0、float は 0.0f、double は 0.0d、char は文字コード 0('\u0000')、boolean は false、参照型は null と定められています。
一方、メソッドの中のローカル変数には既定値が入りません。
public class LocalErr {
public static void main(String[] args) {
int n;
System.out.println(n); // ローカル変数は既定値が入らない
}
}
LocalErr.java:4: error: variable n might not have been initialized
System.out.println(n); // ローカル変数は既定値が入らない
^
1 error
日本語表示は「変数nは初期化されていない可能性があります」です。int n = 0; のように、使う前に値を入れておけば通ります。
オーバーフロー(桁あふれ)を実行結果で見る
ここからは、範囲を超えたときに何が起こるかを確かめます。Overflow.java として保存してください。
public class Overflow {
public static void main(String[] args) {
int max = Integer.MAX_VALUE;
System.out.println("max = " + max);
System.out.println("max + 1 = " + (max + 1));
int price = 50_000;
int count = 50_000;
int total = price * count; // int 同士の掛け算
System.out.println("int の掛け算 = " + total);
long total2 = price * count; // 受け取る変数だけ long にしても同じ
System.out.println("long で受ける = " + total2);
long total3 = (long) price * count; // 片方を long にしてから掛ける
System.out.println("先に long 化 = " + total3);
long msPerYear = 1000 * 60 * 60 * 24 * 365; // L を付け忘れた定数
long msPerYearL = 1000L * 60 * 60 * 24 * 365; // 先頭に L
System.out.println("L なし = " + msPerYear);
System.out.println("L あり = " + msPerYearL);
byte b = 127;
b++;
System.out.println("byte 127 に ++ = " + b);
System.out.println("Math.abs(Integer.MIN_VALUE) = " + Math.abs(Integer.MIN_VALUE));
System.out.println("Long.MAX_VALUE + 1 = " + (Long.MAX_VALUE + 1));
System.out.println("Double.MAX_VALUE * 2 = " + (Double.MAX_VALUE * 2));
int mid = (2_000_000_000 + 2_000_000_000) / 2;
System.out.println("(20億 + 20億) / 2 = " + mid);
System.out.println("20億 + (20億 - 20億) / 2 = " + (2_000_000_000 + (2_000_000_000 - 2_000_000_000) / 2));
}
}
実行結果:
max = 2147483647
max + 1 = -2147483648
int の掛け算 = -1794967296
long で受ける = -1794967296
先に long 化 = 2500000000
L なし = 1471228928
L あり = 31536000000
byte 127 に ++ = -128
Math.abs(Integer.MIN_VALUE) = -2147483648
Long.MAX_VALUE + 1 = -9223372036854775808
Double.MAX_VALUE * 2 = Infinity
(20億 + 20億) / 2 = -147483648
20億 + (20億 - 20億) / 2 = 2000000000
最大値に1を足すと最小値になる
Integer.MAX_VALUE + 1 は、エラーにならず -2147483648(最小値) になりました。時計の針が12の次に1へ戻るのと同じで、上限を超えると下限から数え直します。JLS 4.2.2 には「整数演算子は、オーバーフローやアンダーフローを一切知らせない」という趣旨の記述があります。例外が出ないため、結果を見るまで気づきにくいのが特徴です。
int同士の掛け算は、longで受けても桁あふれする
5万 × 5万 は 25億で、int の最大値(約21億)を超えます。結果は -1794967296 になりました。注目したいのは2つ目です。
long total2 = price * count; // -1794967296
long total3 = (long) price * count; // 2500000000
受け取る変数を long にしただけでは直りません。 price * count は int 同士の計算として先に行われ、桁あふれした結果が long に入るためです。計算の前に片方を long にしておくと、掛け算そのものが long で行われます。
| 書き方 | 結果 | 説明 |
|---|---|---|
int total = price * count; |
-1794967296 | int で計算、int で受ける |
long total = price * count; |
-1794967296 | int で計算してから long に入れる |
long total = (long) price * count; |
2500000000 | 片方を long にしてから計算 |
long price = 50_000; と宣言しておく |
2500000000 | 最初から long で持つ |
Lを付け忘れた定数
1年をミリ秒で表す定数は、315億6000万です。L を付けずに書くと、すべて int の掛け算になります。
long msPerYear = 1000 * 60 * 60 * 24 * 365; // 1471228928
long msPerYearL = 1000L * 60 * 60 * 24 * 365; // 31536000000
コンパイルエラーにも警告にもならず、値だけが変わります。大きな定数を掛け算で書くときは、最初の数に L を付けると覚えておくと安心です。左から順に計算されるので、最初が long であれば以降もすべて long で計算されます。
そのほかの桁あふれ
Math.abs(Integer.MIN_VALUE)は負の数のままです。int の最小値は -2147483648 ですが、+2147483648 は int に入らないためです。- 2つの数の中間を
(a + b) / 2で求めると、足した時点で桁あふれすることがあります。a + (b - a) / 2と書けば、足し算が範囲内に収まります。 - double は桁あふれすると
Infinity(無限大) になります。整数のように値が一周することはありません。
Math.addExact・multiplyExactで桁あふれを例外にする
桁あふれを見逃したくない計算には、Math クラスの 〜Exact メソッドが使えます。範囲を超えると ArithmeticException を投げます。
public class Exact {
interface Calc { Object run(); }
static void show(String label, Calc c) {
try {
System.out.println(label + " = " + c.run());
} catch (ArithmeticException e) {
System.out.println(label + " -> " + e);
}
}
public static void main(String[] args) {
show("Math.addExact(2147483647, 1) ", () -> Math.addExact(Integer.MAX_VALUE, 1));
show("Math.subtractExact(-2147483648, 1) ", () -> Math.subtractExact(Integer.MIN_VALUE, 1));
show("Math.multiplyExact(50000, 50000) ", () -> Math.multiplyExact(50_000, 50_000));
show("Math.multiplyExact(50000L, 50000L) ", () -> Math.multiplyExact(50_000L, 50_000L));
show("Math.addExact(Long.MAX_VALUE, 1L) ", () -> Math.addExact(Long.MAX_VALUE, 1L));
show("Math.incrementExact(2147483647) ", () -> Math.incrementExact(Integer.MAX_VALUE));
show("Math.negateExact(-2147483648) ", () -> Math.negateExact(Integer.MIN_VALUE));
show("Math.absExact(-2147483648) ", () -> Math.absExact(Integer.MIN_VALUE));
show("Math.toIntExact(3000000000L) ", () -> Math.toIntExact(3_000_000_000L));
show("Math.toIntExact(123L) ", () -> Math.toIntExact(123L));
show("Math.addExact(1, 2) ", () -> Math.addExact(1, 2));
}
}
実行結果:
Math.addExact(2147483647, 1) -> java.lang.ArithmeticException: integer overflow
Math.subtractExact(-2147483648, 1) -> java.lang.ArithmeticException: integer overflow
Math.multiplyExact(50000, 50000) -> java.lang.ArithmeticException: integer overflow
Math.multiplyExact(50000L, 50000L) = 2500000000
Math.addExact(Long.MAX_VALUE, 1L) -> java.lang.ArithmeticException: long overflow
Math.incrementExact(2147483647) -> java.lang.ArithmeticException: integer overflow
Math.negateExact(-2147483648) -> java.lang.ArithmeticException: integer overflow
Math.absExact(-2147483648) -> java.lang.ArithmeticException: Overflow to represent absolute value of Integer.MIN_VALUE
Math.toIntExact(3000000000L) -> java.lang.ArithmeticException: integer overflow
Math.toIntExact(123L) = 123
Math.addExact(1, 2) = 3
例外の文面は、int の計算なら integer overflow、long の計算なら long overflow です。範囲内に収まる計算は、通常の演算子と同じ結果を返します。
例外を捕まえずにそのまま動かしたときの表示も載せておきます。
public class ExactTrace {
public static void main(String[] args) {
int price = 50_000;
int count = 50_000;
int total = Math.multiplyExact(price, count);
System.out.println(total);
}
}
Exception in thread "main" java.lang.ArithmeticException: integer overflow
at java.base/java.lang.Math.multiplyExact(Math.java:992)
at ExactTrace.main(ExactTrace.java:5)
スタックトレースに行番号(ExactTrace.java:5)が出るので、どの計算で範囲を超えたかがすぐにわかります。
Exact系メソッドの一覧
Math クラスの API ドキュメント(Java SE 21)に載っている主なメソッドです。いずれも int 版と long 版があります。
| メソッド | 計算 | 追加されたバージョン |
|---|---|---|
addExact(x, y) |
足し算 | 1.8 |
subtractExact(x, y) |
引き算 | 1.8 |
multiplyExact(x, y) |
掛け算 | 1.8(long と int の組み合わせ版は 9) |
incrementExact(a) / decrementExact(a) |
1 を足す/引く | 1.8 |
negateExact(a) |
符号の反転 | 1.8 |
toIntExact(value) |
long から int への変換 | 1.8 |
absExact(a) |
絶対値 | 15 |
divideExact(x, y) / floorDivExact(x, y) / ceilDivExact(x, y) |
割り算 | 18 |
桁あふれへの対策の選び方
| 状況 | 対策 |
|---|---|
| 21億を超え得るとわかっている | 最初から long で宣言する |
| int の変数同士を掛けて long で受ける | 片方に (long) を付ける |
| 大きな定数を書く | 最初の数に L を付ける |
| 範囲を超えたら処理を止めて知らせたい | Math.addExact・multiplyExact などを使う |
| long を int に戻す | (int) の代わりに Math.toIntExact を使う |
| long でも足りない | BigInteger を使う |
0.1 + 0.2 の誤差とBigDecimal
float と double は、数を2進数の小数で持ちます(JLS 4.2.3 では、float は IEEE 754 の binary32、double は binary64 に対応するとされています)。0.1 のような10進数の小数は2進数ではきっちり表せないため、ごく近い値で代用されます。
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
public class Floating {
public static void main(String[] args) {
System.out.println("0.1 + 0.2 = " + (0.1 + 0.2));
System.out.println("0.1 + 0.2 == 0.3 = " + (0.1 + 0.2 == 0.3));
System.out.println("0.1f + 0.2f = " + (0.1f + 0.2f));
System.out.println("0.1f == 0.1 = " + (0.1f == 0.1));
System.out.println("(double) 0.1f = " + (double) 0.1f);
System.out.println("1.1 * 3 = " + (1.1 * 3));
double sum = 0;
for (int i = 0; i < 10; i++) sum += 0.1;
System.out.println("0.1 を10回足す = " + sum);
System.out.println("16777217f = " + 16777217f);
System.out.println("9007199254740993L -> double = " + (double) 9007199254740993L);
System.out.println("差の絶対値で比較 = " + (Math.abs((0.1 + 0.2) - 0.3) < 1e-9));
System.out.println("NaN == NaN = " + (Double.NaN == Double.NaN));
System.out.println();
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
System.out.println("BigDecimal 0.1 + 0.2 = " + a.add(b));
System.out.println("equals(0.3) = " + a.add(b).equals(new BigDecimal("0.3")));
System.out.println("new BigDecimal(0.1) = " + new BigDecimal(0.1));
System.out.println("BigDecimal.valueOf(0.1) = " + BigDecimal.valueOf(0.1));
System.out.println("2.0 equals 2.00 = " + new BigDecimal("2.0").equals(new BigDecimal("2.00")));
System.out.println("2.0 compareTo 2.00 = " + new BigDecimal("2.0").compareTo(new BigDecimal("2.00")));
try {
System.out.println(new BigDecimal("1").divide(new BigDecimal("3")));
} catch (ArithmeticException e) {
System.out.println("1 / 3 -> " + e);
}
System.out.println("1 / 3 (小数4桁) = " + new BigDecimal("1").divide(new BigDecimal("3"), 4, RoundingMode.HALF_UP));
BigInteger big = BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE);
System.out.println("Long.MAX_VALUE + 1 (BigInteger) = " + big);
System.out.println("2 の 100 乗 = " + BigInteger.TWO.pow(100));
}
}
実行結果:
0.1 + 0.2 = 0.30000000000000004
0.1 + 0.2 == 0.3 = false
0.1f + 0.2f = 0.3
0.1f == 0.1 = false
(double) 0.1f = 0.10000000149011612
1.1 * 3 = 3.3000000000000003
0.1 を10回足す = 0.9999999999999999
16777217f = 1.6777216E7
9007199254740993L -> double = 9.007199254740992E15
差の絶対値で比較 = true
NaN == NaN = false
BigDecimal 0.1 + 0.2 = 0.3
equals(0.3) = true
new BigDecimal(0.1) = 0.1000000000000000055511151231257827021181583404541015625
BigDecimal.valueOf(0.1) = 0.1
2.0 equals 2.00 = false
2.0 compareTo 2.00 = 0
1 / 3 -> java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
1 / 3 (小数4桁) = 0.3333
Long.MAX_VALUE + 1 (BigInteger) = 9223372036854775808
2 の 100 乗 = 1267650600228229401496703205376
doubleで押さえておきたいこと
0.1 + 0.2は0.30000000000000004になり、== 0.3は false です。小数同士を==で比べる代わりに、差の絶対値が十分小さいかどうかで判定します。- 0.1 を10回足しても 1.0 ちょうどにはなりません(0.9999999999999999)。ループの終了条件に小数の
==を使うと、止まらないループになることがあります。回数は int で数えるのが確実です。 0.1f + 0.2fは 0.3 と表示されますが、これは float の表示桁数が少ないためで、誤差がないわけではありません。(double) 0.1fは 0.10000000149011612 です。float と double を混ぜて比較すると false になります。- 大きな整数は float・double に入れると丸められます。 float は 16,777,217 を、double は 9,007,199,254,740,993 を正確に持てませんでした。ID のような「1 でも違えば別物」の整数は long で持ちます。
NaN(数でない値)は自分自身とも等しくなりません。 判定にはDouble.isNaN()を使います。
BigDecimalの使い方の要点
- 文字列から作る:
new BigDecimal("0.1")。new BigDecimal(0.1)は double の誤差をそのまま引き継ぎ、0.1000000000000000055… になります。double の変数から作るときはBigDecimal.valueOf(0.1)を使います。 - 比較は
compareTo:equalsは桁数(スケール)まで見るため、2.0 と 2.00 は false になります。値として等しいかどうかはcompareTo(...) == 0で調べます。 - 割り算は桁数と丸め方を指定する:1 ÷ 3 のように割り切れない計算は、指定がないと
ArithmeticException: Non-terminating decimal expansionになります。divide(x, 4, RoundingMode.HALF_UP)のように、小数点以下の桁数と丸め方を渡します。
BigDecimal・BigIntegerへの切り替え目安
| 扱う値 | 目安 | 選ぶ型 |
|---|---|---|
| 金額、単価 × 数量、税率、割引率 | 1円・1銭の違いが結果に影響する | BigDecimal(または最小単位の整数にして long) |
小数の合計を == で照合する |
10進数どおりの一致が必要 | BigDecimal |
| 平均、割合、座標、計測値、グラフ用の数値 | 15桁ほどの精度で足りる | double |
| 回数の多い数値計算(統計、シミュレーション) | 速度を優先する | double |
| 約922京を超える整数(階乗、大きなべき乗) | long に収まらない | BigInteger |
| 約21億を超える整数 | long に収まる | long |
「10進数で書いたとおりの結果が必要かどうか」が切り替えの基準です。BigDecimal は基本型より計算に時間がかかり、演算子(+ など)ではなくメソッド(add・multiply)で書く必要があります。すべてを置き換えるのではなく、正確さが要る箇所に絞って使います。
charの演算結果
char は「文字」を入れる型ですが、中身は 0〜65535 の整数(文字コード)です。そのため、計算すると数値になります。
public class CharCalc {
public static void main(String[] args) {
char c = 'a';
System.out.println("'a' + 1 = " + ('a' + 1));
System.out.println("(char)('a' + 1) = " + (char) ('a' + 1));
System.out.println("'a' + 'b' = " + ('a' + 'b'));
System.out.println("\"\" + 'a' + 'b' = " + "" + 'a' + 'b');
System.out.println("'a' + 'b' + \"\" = " + ('a' + 'b' + ""));
c++;
System.out.println("c++ = " + c);
c += 1;
System.out.println("c += 1 = " + c);
System.out.println("(int) 'A' = " + (int) 'A');
System.out.println("(int) 'あ' = " + (int) 'あ');
System.out.println("(char) 65 = " + (char) 65);
System.out.println("'7' - '0' = " + ('7' - '0'));
char max = Character.MAX_VALUE;
max++;
System.out.println("MAX_VALUE に ++ = " + (int) max);
StringBuilder sb = new StringBuilder();
for (char x = 'A'; x <= 'E'; x++) sb.append(x);
System.out.println("A から E = " + sb);
String s = "𠮷野家";
System.out.println("length() = " + s.length());
System.out.println("codePointCount = " + s.codePointCount(0, s.length()));
}
}
実行結果:
'a' + 1 = 98
(char)('a' + 1) = b
'a' + 'b' = 195
"" + 'a' + 'b' = ab
'a' + 'b' + "" = 195
c++ = b
c += 1 = c
(int) 'A' = 65
(int) 'あ' = 12354
(char) 65 = A
'7' - '0' = 7
MAX_VALUE に ++ = 0
A から E = ABCDE
length() = 4
codePointCount = 3
| 式 | 結果 | 理由 |
|---|---|---|
'a' + 1 |
98 | char は int に変換されてから計算される('a' は 97) |
(char) ('a' + 1) |
b | char に戻すと次の文字になる |
'a' + 'b' |
195 | 文字の連結ではなく、97 + 98 の足し算 |
"" + 'a' + 'b' |
ab | 先に文字列があると、以降は文字列の連結になる |
'a' + 'b' + "" |
195 | 左から計算するので、先に足し算が行われる |
c++、c += 1 |
次の文字 | 複合代入は結果を自動で char に戻す |
'7' - '0' |
7 | 数字の文字を数値に直す定番の書き方 |
Character.MAX_VALUE に ++ |
0 | char も範囲を超えると一周する |
文字をつなげたいときは String に変換してから(String.valueOf(c) や "" + c)、次の文字がほしいときはキャストで char に戻す、と使い分けます。
最後の2行は、1つの char に入りきらない文字の例です。「𠮷」は char 2つ分(サロゲートペア)で表されるため、3文字の文字列でも length() は 4 を返します。見た目の文字数を数えたいときは codePointCount を使います。
型変換(キャスト)で値がどう変わるか
小さい型から大きい型への代入は自動で行われ、逆はキャストが必要です。キャストは「入りきらない部分を落とす」操作なので、結果が直感と違うことがあります。次の式を実行した結果です。
| 式 | 結果 | 何が起きたか |
|---|---|---|
(int) 3.99 |
3 | 小数点以下は切り捨て(四捨五入ではない) |
(int) -3.99 |
-3 | 0 に近い方へ切り捨て |
(int) 3_000_000_000L |
-1294967296 | 上位のビットが落ちる |
(byte) 200 |
-56 | 同上 |
(int) 1e20 |
2147483647 | 小数から整数へは、範囲を超えると最大値に張り付く |
(int) Double.NaN |
0 | NaN は 0 になる |
7 / 2 |
3 | int 同士の割り算は小数点以下を捨てる |
7 / 2.0 |
3.5 | 片方が double なら double で計算 |
-7 / 2、-7 % 2 |
-3、-1 | 商は 0 に近い方へ、余りの符号は割られる数に合わせる |
1 / 0 |
ArithmeticException: / by zero |
整数の 0 割りは例外 |
1.0 / 0 |
Infinity | 小数の 0 割りは例外にならない |
0.0 / 0 |
NaN | 同上 |
四捨五入したいときは Math.round(3.99)、long から int へ安全に変換したいときは Math.toIntExact を使います。
文字列からの変換とラッパー型
文字列を数値にする場合も、範囲を超えると知らせてくれます。
System.out.println(Integer.parseInt("123") + " " + Long.parseLong("3000000000"));
try { Integer.parseInt("3000000000"); } catch (NumberFormatException ex) { System.out.println(ex); }
Integer a = 127, b2 = 127, c = 128, d2 = 128;
System.out.println((a == b2) + " " + (c == d2) + " " + c.equals(d2));
123 3000000000
java.lang.NumberFormatException: For input string: "3000000000"
true false true
3行目はラッパー型の比較です。Integer 同士を == で比べると、127 では true、128 では false になりました。== は「同じオブジェクトかどうか」を調べるためです。ラッパー型の値を比べるときは equals を使います。また、ラッパー型の変数は null を持てるので、null のまま基本型へ代入すると NullPointerException になります(同じプログラムで確認)。
よくある質問
Q. intとlongはどちらを使えばよいですか?
A. 基本は int で、約21億を超える可能性がある値は long にします。ミリ秒の時刻、ファイルサイズ(バイト数)、累計の件数、ID は long が向いています。int の変数同士を掛けて long で受けるときは、片方に (long) を付けてから計算してください。
Q. floatとdoubleの違いは何ですか?
A. float は32ビットで有効桁が約7桁、double は64ビットで約15〜16桁です。小数リテラルは何も付けなければ double なので、通常は double を使います。float は、大量の数値を配列で持つなど、メモリの量を抑えたい場面で選びます。
Q. オーバーフローしても例外が出ないのはなぜですか?
A. Javaの整数演算は、範囲を超えた分を落として計算を続けると言語仕様で決められているためです。例外で知らせてほしい計算には Math.addExact・Math.multiplyExact などを使うと、ArithmeticException: integer overflow が投げられます。
Q. Stringは基本データ型ですか?
A. いいえ。String はクラス(参照型)で、基本データ型は8種類だけです。String のフィールドの既定値は null で、比較には equals を使います。
まとめ
- 基本データ型は8種類:byte・short・int・long(整数)、float・double(小数)、char(文字)、boolean(真偽)。範囲はどの環境でも同じ。
- 整数リテラルは int、小数リテラルは double。long には
L、float にはfを付ける。long b = 3000000000;は「整数が大きすぎます」のエラーになる。 - 既定値が入るのはフィールドと配列の要素。ローカル変数は代入してから使う。
- 整数の桁あふれは例外にならず、値が一周する。int 同士の掛け算は long で受けても直らないので、計算の前に
(long)を付けるか、定数の先頭にLを付ける。 Math.addExact・multiplyExact・toIntExactは、範囲を超えるとArithmeticException: integer overflow(long はlong overflow)を投げる。0.1 + 0.2は0.30000000000000004。10進数どおりの結果が必要な計算は、文字列から作ったBigDecimalに切り替える。- char の計算結果は int。
'a' + 1は 98、(char) ('a' + 1)は b。
参考・出典
- The Java Language Specification, Java SE 21 Edition「Chapter 4. Types, Values, and Variables」(4.2 Primitive Types and Values、4.2.1〜4.2.3、4.2.5、4.12.5 Initial Values of Variables)https://docs.oracle.com/javase/specs/jls/se21/html/jls-4.html
- Java SE 21 API ドキュメント「Class Math」(addExact・multiplyExact・toIntExact ほか)https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Math.html
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Javaデータ型一覧|基本型8種の範囲・最大値とオーバーフロー」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。