Ch 3.3 산술 연산자 (Arithmetic Operator)
산술 연산자는 수학에서 다루는 사칙연산(+, -, *, /)과 나머지 연산(%)을 포함합니다. 단순해 보이지만 자동 형변환, 정수 오버플로우, 부동소수점 정밀도 등 알아야 할 중요한 개념들이 많습니다.
1. 산술 연산자 종류
| 연산자 | 이름 | 예시 | 결과 |
|---|---|---|---|
+ | 덧셈 / 문자열 연결 | 3 + 4 | 7 |
- | 뺄셈 | 10 - 3 | 7 |
* | 곱셈 | 4 * 5 | 20 |
/ | 나눗셈 | 10 / 3 | 3 (정수 버림) |
% | 나머지 | 10 % 3 | 1 |
2. 덧셈 연산자 (+): 숫자 더하기 + 문자열 연결
+ 연산자는 자바에서 두 가지 역할 을 합니다.
- 숫자 덧셈: 두 숫자를 더합니다.
- 문자열 연결(Concatenation): 피연산자 중 하나라도
String이면 나머지를 문자열로 변환하여 이어 붙입니다.
public class PlusOperator {
public static void main(String[] args) {
// 숫자 덧셈
int a = 10, b = 20;
System.out.println(a + b); // 30
// 문자열 연결: 한 쪽이 String이면 나머지도 String으로 자동 변환
String hello = "Hello";
System.out.println(hello + 42); // "Hello42"
System.out.println(hello + " " + "World"); // "Hello World"
System.out.println("나이: " + a); // "나이: 10"
}
}
순서(결합 방향)에 따른 차이
+ 연산자는 좌결합(왼쪽부터) 입니다. 따라서 피연산자의 순서에 따라 결과가 완전히 달라집니다.
public class PlusOrder {
public static void main(String[] args) {
// 1 + 2 = 3 (숫자) → 3 + "3" = "33" (문자열 연결)
System.out.println(1 + 2 + "3"); // "33"
// "1" + 2 = "12" (문자열 연결) → "12" + 3 = "123" (문자열 연결)
System.out.println("1" + 2 + 3); // "123"
// "1" + (2 + 3) = "1" + 5 = "15" (괄호로 덧셈을 먼저)
System.out.println("1" + (2 + 3)); // "15"
// 숫자 먼저 계산 후 문자열 변환
int x = 5, y = 10;
System.out.println("합계: " + (x + y)); // "합계: 15" (괄호 필수!)
System.out.println("합계: " + x + y); // "합계: 510" (의도와 다름!)
}
}
"합계: " + x + y 는 "합계: 5" + 10 = "합계: 510" 이 됩니다.
숫자를 먼저 더하려면 "합계: " + (x + y) 처럼 괄호로 묶어야 합니다.
3. 뺄셈(-)과 곱셈(*)
기본 수학 연산과 동일합니다.
public class SubMul {
public static void main(String[] args) {
int a = 15, b = 4;
// 뺄셈
System.out.println(a - b); // 11
System.out.println(b - a); // -11 (음수 가능)
// 곱셈
System.out.println(a * b); // 60
// 실수 연산
double x = 3.14, r = 5.0;
double area = x * r * r; // 원의 넓이 (π * r²)
System.out.printf("원의 넓이: %.2f%n", area); // 78.50
}
}
4. 나눗셈 연산자 (/)
정수 나눗셈: 소수점 버림(Truncate)
두 피연산자가 모두 정수형 이면 결과도 정수입니다. 소수점 아래는 버립니다(반올림 아님).
System.out.println(10 / 4); // 2 (소수점 버림, 반올림 아님)
System.out.println(-10 / 3); // -3 (0 방향으로 버림)
System.out.println(7 / 2); // 3
System.out.println(1 / 2); // 0
실수 나눗셈: 소수점 포함
피연산자 중 하나라도 실수형 이면 결과도 실수입니다.
System.out.println(10 / 4.0); // 2.5 (한 쪽이 double → 결과도 double)
System.out.println(10.0 / 4); // 2.5
System.out.println((double)10 / 4); // 2.5 (명시적 형변환)
0으로 나누기
public class DivisionByZero {
public static void main(String[] args) {
// 정수를 0으로 나누면 ArithmeticException 발생 (런타임 에러)
// System.out.println(5 / 0); // ArithmeticException: / by zero
// 실수를 0으로 나누면 Infinity 또는 NaN 반환
System.out.println(5.0 / 0.0); // Infinity
System.out.println(-5.0 / 0.0); // -Infinity
System.out.println(0.0 / 0.0); // NaN (Not a Number)
// Infinity, NaN 체크
double result = 5.0 / 0.0;
System.out.println(Double.isInfinite(result)); // true
System.out.println(Double.isNaN(0.0 / 0.0)); // true
}
}
실수형 나눗셈은 예외 없이 Infinity를 반환하지만, 정수형 나눗셈은 ArithmeticException을 던집니다. 사용자 입력값으로 나눌 때는 반드시 0 체크를 해야 합니다.
int divisor = getUserInput();
if (divisor != 0) {
System.out.println(100 / divisor);
} else {
System.out.println("0으로 나눌 수 없습니다.");
}
5. 나머지 연산자 (%)
왼쪽 피연산자를 오른쪽으로 나눈 나머지를 반환합니다.
public class ModuloOperator {
public static void main(String[] args) {
System.out.println(10 % 3); // 1
System.out.println(10 % 5); // 0 (나누어 떨어짐)
System.out.println(7 % 2); // 1
System.out.println(3 % 10); // 3 (피제수 < 제수)
}
}
음수 피연산자와 나머지
자바에서 %의 결과 부호는 왼쪽 피연산자(피제수) 의 부호를 따릅니다.
System.out.println(10 % 3); // 1
System.out.println(-10 % 3); // -1 (피제수가 음수이므로 결과도 음수)
System.out.println(10 % -3); // 1 (피제수가 양수이므로 결과도 양수)
System.out.println(-10 % -3); // -1
Math.floorMod vs %
수학적 나머지(항상 양수)를 원한다면 Math.floorMod()를 사용하세요.
System.out.println(-7 % 3); // -1 (Java % 연산자)
System.out.println(Math.floorMod(-7, 3)); // 2 (수학적 나머지, 항상 양수)
나머지 연산자 활용
public class ModuloUsage {
public static void main(String[] args) {
// 짝수/홀수 판별
for (int i = 1; i <= 10; i++) {
String type = (i % 2 == 0) ? "짝수" : "홀수";
System.out.println(i + ": " + type);
}
// 배수 판별
int n = 36;
System.out.println(n + "은 3의 배수: " + (n % 3 == 0)); // true
System.out.println(n + "은 7의 배수: " + (n % 7 == 0)); // false
// 순환 인덱스 (0, 1, 2, 0, 1, 2, ...)
int size = 3;
for (int i = 0; i < 9; i++) {
System.out.print((i % size) + " "); // 0 1 2 0 1 2 0 1 2
}
System.out.println();
// 자릿수 추출
int number = 12345;
System.out.println("1의 자리: " + (number % 10)); // 5
System.out.println("10의 자리: " + (number / 10 % 10)); // 4
}
}
6. 산술 연산 시 자동 형변환 규칙
서로 다른 타입의 피연산자가 연산될 때, 자바는 더 큰(정밀한) 타입으로 자동 변환(프로모션) 합니다.
| 피연산자 타입 조합 | 결과 타입 |
|---|---|
byte + byte | int |
short + short | int |
int + long | long |
int + float | float |
long + float | float |
float + double | double |
모든 숫자 + String | String |
public class TypePromotion {
public static void main(String[] args) {
byte b = 10;
short s = 20;
int i = 30;
long l = 40L;
float f = 1.5f;
double d = 2.5;
// byte + short → int
int r1 = b + s;
System.out.println("byte+short = " + r1); // 30 (int)
// int + long → long
long r2 = i + l;
System.out.println("int+long = " + r2); // 70 (long)
// int + float → float
float r3 = i + f;
System.out.println("int+float = " + r3); // 31.5 (float)
// long + double → double
double r4 = l + d;
System.out.println("long+double = " + r4); // 42.5 (double)
}
}
7. 정수 오버플로우와 해결법
정수형 변수가 표현할 수 있는 범위를 초과하면 오버플로우(Overflow) 가 발생하여 예상치 못한 값이 됩니다.
public class OverflowExample {
public static void main(String[] args) {
// int 최댓값: 2,147,483,647
int max = Integer.MAX_VALUE;
System.out.println("int 최댓값: " + max); // 2147483647
System.out.println("max + 1: " + (max + 1)); // -2147483648 (오버플로우!)
// 실제 문제 사례: 두 큰 수의 합
int a = 2_000_000_000;
int b = 2_000_000_000;
int wrong = a + b; // 오버플로우 발생!
System.out.println("잘못된 합: " + wrong); // 음수 출력
// 해결법 1: long 타입 사용
long correct = (long) a + b;
System.out.println("올바른 합: " + correct); // 4000000000
// 해결법 2: Math.addExact() 사용 (오버플로우 시 ArithmeticException 던짐)
try {
int safeResult = Math.addExact(a, b);
System.out.println(safeResult);
} catch (ArithmeticException e) {
System.out.println("오버플로우 감지됨! long 타입을 사용하세요.");
}
// Math.multiplyExact, Math.subtractExact 도 동일하게 사용 가능
}
}
8. 부동소수점 정밀도 문제와 BigDecimal
컴퓨터는 실수를 이진수로 표현하기 때문에, 일부 소수는 정확하게 표현할 수 없습니다.
public class FloatingPointPrecision {
public static void main(String[] args) {
// 유명한 부동소수점 문제
System.out.println(0.1 + 0.2); // 0.30000000000000004 (!)
System.out.println(0.1 + 0.2 == 0.3); // false
// double은 약 15~16자리 유효 숫자
System.out.println(1.0 / 3.0); // 0.3333333333333333 (반복 소수 잘림)
// 해결법 1: 허용 오차(epsilon) 비교
double a = 0.1 + 0.2;
double b = 0.3;
double epsilon = 1e-10;
System.out.println(Math.abs(a - b) < epsilon); // true
// 해결법 2: BigDecimal 사용 (정밀 계산)
java.math.BigDecimal bd1 = new java.math.BigDecimal("0.1");
java.math.BigDecimal bd2 = new java.math.BigDecimal("0.2");
java.math.BigDecimal bd3 = bd1.add(bd2);
System.out.println(bd3); // 0.3 (정확!)
System.out.println(bd3.equals(new java.math.BigDecimal("0.3"))); // true
}
}
- 금융 계산 (돈 계산)
- 세금, 이자율 계산
- 정확한 소수점 자리수가 필요한 경우
일반적인 과학 계산이나 게임 물리 계산은 double로 충분합니다.
9. Math 클래스 주요 메서드
java.lang.Math 클래스는 수학 관련 정적 메서드를 제공합니다. import 없이 바로 사용할 수 있습니다.
public class MathMethods {
public static void main(String[] args) {
// 절댓값
System.out.println(Math.abs(-15)); // 15
System.out.println(Math.abs(15)); // 15
// 최댓값, 최솟값
System.out.println(Math.max(10, 20)); // 20
System.out.println(Math.min(10, 20)); // 10
// 거듭제곱: pow(밑, 지수)
System.out.println(Math.pow(2, 10)); // 1024.0
System.out.println(Math.pow(3, 3)); // 27.0
// 제곱근
System.out.println(Math.sqrt(16)); // 4.0
System.out.println(Math.sqrt(2)); // 1.4142135623730951
// 올림, 내림, 반올림
System.out.println(Math.ceil(3.1)); // 4.0 (올림)
System.out.println(Math.ceil(-3.9)); // -3.0 (올림: 0 방향)
System.out.println(Math.floor(3.9)); // 3.0 (내림)
System.out.println(Math.floor(-3.1)); // -4.0 (내림: -∞ 방향)
System.out.println(Math.round(3.5)); // 4 (반올림 → long/int 반환)
System.out.println(Math.round(3.49)); // 3
// 로그, 지수
System.out.println(Math.log(Math.E)); // 1.0 (자연로그 ln(e))
System.out.println(Math.log10(1000)); // 3.0 (상용로그)
System.out.println(Math.exp(1)); // 2.718... (e^1)
// 삼각함수 (라디안 단위)
System.out.printf("sin(90°) = %.1f%n", Math.sin(Math.PI / 2)); // 1.0
System.out.printf("cos(0°) = %.1f%n", Math.cos(0)); // 1.0
// 랜덤 값: 0.0 이상 1.0 미만
double rand = Math.random();
System.out.println("랜덤: " + rand);
// 1~6 주사위 시뮬레이션
int dice = (int)(Math.random() * 6) + 1;
System.out.println("주사위: " + dice);
// PI, E 상수
System.out.println("PI = " + Math.PI); // 3.141592653589793
System.out.println("E = " + Math.E); // 2.718281828459045
}
}
10. 실전 예제: BMI 계산기
import java.util.Scanner;
public class BMICalculator {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("키를 입력하세요 (cm): ");
double heightCm = scanner.nextDouble();
System.out.print("몸무게를 입력하세요 (kg): ");
double weight = scanner.nextDouble();
// BMI 계산: 체중(kg) / 키(m)²
double heightM = heightCm / 100.0; // cm → m 변환
double bmi = weight / (heightM * heightM);
// 소수점 2자리로 표시
System.out.printf("%n===== BMI 계산 결과 =====%n");
System.out.printf("키 : %.1f cm%n", heightCm);
System.out.printf("몸무게: %.1f kg%n", weight);
System.out.printf("BMI : %.2f%n", bmi);
// BMI 판정 (WHO 기준)
String category;
if (bmi < 18.5) {
category = "저체중 (Underweight)";
} else if (bmi < 23.0) {
category = "정상 (Normal)";
} else if (bmi < 25.0) {
category = "과체중 (Overweight)";
} else if (bmi < 30.0) {
category = "비만 1단계 (Obese Class I)";
} else {
category = "비만 2단계 이상 (Obese Class II+)";
}
System.out.println("판정 : " + category);
// 정상 체중 범위 계산
double minWeight = 18.5 * heightM * heightM;
double maxWeight = 22.9 * heightM * heightM;
System.out.printf("정상 체중 범위: %.1f kg ~ %.1f kg%n", minWeight, maxWeight);
// 목표 체중까지 차이
double targetWeight = 22.0 * heightM * heightM; // 정상 중간값
double diff = targetWeight - weight;
if (Math.abs(diff) < 0.5) {
System.out.println("이상적인 체중입니다!");
} else if (diff > 0) {
System.out.printf("목표 체중까지 %.1f kg 증량이 필요합니다.%n", diff);
} else {
System.out.printf("목표 체중까지 %.1f kg 감량이 필요합니다.%n", Math.abs(diff));
}
scanner.close();
}
}
예시 실행 결과 (키 175cm, 몸무게 70kg 입력 시):
키를 입력하세요 (cm): 175
몸무게를 입력하세요 (kg): 70
===== BMI 계산 결과 =====
키 : 175.0 cm
몸무게: 70.0 kg
BMI : 22.86
판정 : 정상 (Normal)
정상 체중 범위: 56.7 kg ~ 70.0 kg
이상적인 체중입니다!
11. 핵심 정리
| 주제 | 핵심 내용 |
|---|---|
| 정수 나눗셈 | 소수점 버림, 실수 결과가 필요하면 (double) 캐스팅 |
| 0 나누기 | 정수: ArithmeticException, 실수: Infinity / NaN |
문자열 + | 순서에 따라 결과가 달라짐, 숫자 합산은 괄호 사용 |
| 자동 형변환 | 더 큰 타입으로 자동 승격 (int → long → float → double) |
| 오버플로우 | long 사용 또는 Math.addExact() 로 안전 처리 |
| 부동소수점 | 0.1 + 0.2 ≠ 0.3, 금융 계산에는 BigDecimal 사용 |
| Math 클래스 | abs, max, min, pow, sqrt, ceil, floor, round |