산술 연산과 문자열 결합
정수·실수 산술과 문자열 결합의 평가 순서를 추적하고 회원 나이의 합계·평균·자격 비율을 계산합니다.
연산자는 값을 계산하지만 모든 +가 덧셈은 아닙니다.
피연산자 중 문자열이 나타난 뒤의 +는 문자열 결합으로 동작합니다.
또 정수끼리 나누면 소수 부분이 사라진 뒤 결과가 다른 타입에 대입됩니다.
회원가입의 합계와 평균을 정확히 만들려면 식의 최종 변수 타입이 아니라 각 연산이 수행되는 순간의 타입과 순서를 읽어야 합니다.
문자열 연결 순서
public final class ConcatenationTrap {
public static void main(String[] args) {
int firstAge = 20;
int secondAge = 30;
System.out.println("total=" + firstAge + secondAge);
}
}total=2030식은 왼쪽부터 평가됩니다.
먼저 "total=" + firstAge가 문자열 "total=20"을 만들고, 다음 + secondAge도 문자열 결합이 되어 "total=2030"이 됩니다.
괄호로 숫자 덧셈을 먼저 계산하면 결과가 달라집니다.
System.out.println("total=" + (firstAge + secondAge));total=50출력 한 줄에서 모든 계산을 끝내기보다 합계에 이름을 붙이면 평가 순서와 단위를 더 분명하게 볼 수 있습니다.
public final class MemberAgeArithmetic {
public static void main(String[] args) {
int firstAge = 20;
int secondAge = 30;
int memberCount = 2;
int totalAge = firstAge + secondAge;
int differenceAge = secondAge - firstAge;
int doubledAge = firstAge * 2;
int wholeAverage = totalAge / memberCount;
int remainder = totalAge % memberCount;
double preciseAverage = (double) totalAge / memberCount;
System.out.println("total=" + totalAge);
System.out.println("difference=" + differenceAge);
System.out.println("doubled=" + doubledAge);
System.out.println("wholeAverage=" + wholeAverage);
System.out.println("remainder=" + remainder);
System.out.println("preciseAverage=" + preciseAverage);
}
}total=50
difference=10
doubled=40
wholeAverage=25
remainder=0
preciseAverage=25.0+, -, *, /, %는 각각 덧셈, 뺄셈, 곱셈, 나눗셈의 몫, 나머지를 계산합니다.
음수의 나머지 규칙까지 필요한 알고리즘에서는 부호를 별도로 확인해야 하지만 현재 회원 나이는 음수를 허용하지 않을 예정입니다.
정수 나눗셈
public final class IntegerDivisionLoss {
public static void main(String[] args) {
int totalAge = 5;
int memberCount = 2;
double average = totalAge / memberCount;
System.out.println("average=" + average);
}
}average=2.0totalAge / memberCount의 두 피연산자가 모두 int이므로 먼저 정수 결과 2가 만들어집니다.
그 뒤 double 변수에 대입되면서 2.0으로 바뀝니다.
이미 버린 0.5는 대입 변환으로 복구되지 않습니다.
double average = (double) totalAge / memberCount;왼쪽 피연산자를 double로 바꾸면 다른 피연산자도 큰 범위로 변환되고 실수 나눗셈 2.5가 됩니다.
형변환 자체는 마지막 절에서 자세히 다루지만 평균 계산에서 나눗셈 전에 타입을 바꿔야 한다는 위치는 지금 확인합니다.
0으로 정수를 나누면 실행 중 예외가 발생합니다.
public final class DivideByZero {
public static void main(String[] args) {
int totalAge = 50;
int memberCount = 0;
System.out.println(totalAge / memberCount);
}
}Exception in thread "main" java.lang.ArithmeticException: / by zero컴파일러는 memberCount의 실행 시점 값이 항상 0인지 일반적으로 판단하지 않습니다.
평균을 계산하기 전에 회원 수가 0인지 조건으로 나누어야 합니다.
연산자 우선순위
곱셈·나눗셈·나머지는 덧셈·뺄셈보다 먼저 평가됩니다.
int score = 10 + 20 * 2; // 50
int grouped = (10 + 20) * 2; // 60대입은 산술 계산 뒤에 일어나고 문자열 결합은 같은 + 연산자끼리 왼쪽부터 진행됩니다.
우선순위 표를 전부 외워 복잡한 식을 압축하기보다 계산의 의미 단위에 괄호를 둡니다.
public final class DurationBreakdown {
public static void main(String[] args) {
int totalMinutes = 135;
int hours = totalMinutes / 60;
int remainingMinutes = totalMinutes % 60;
int restoredMinutes = (hours * 60) + remainingMinutes;
System.out.println(hours + "h " + remainingMinutes + "m");
System.out.println("restored=" + restoredMinutes);
}
}2h 15m
restored=135몫은 시간, 나머지는 남은 분입니다.
(hours * 60) + remainingMinutes는 곱셈이 먼저이므로 괄호 없이도 같지만 “시간을 분으로 환산한 값”이라는 의미를 눈에 보이게 합니다.
다시 계산한 값이 원래 합계와 같은지 확인하면 분해 공식을 검증할 수 있습니다.
나이 차이와 가입 자격 비율
현재 회원 한 명의 정보에는 나이와 최소 가입 나이가 있습니다.
MemberEligibilityMath <email> <age> <minimumAge> 형식으로 받고 계산을 출력과 분리합니다.
public final class MemberEligibilityMath {
public static void main(String[] args) {
if (args.length != 3) {
System.out.println("사용법: MemberEligibilityMath <email> <age> <minimumAge>");
return;
}
String email = args[0];
int age = Integer.parseInt(args[1]);
int minimumAge = Integer.parseInt(args[2]);
int gapYears = minimumAge - age;
double eligibilityPercent = ((double) age / minimumAge) * 100.0;
System.out.println("email=" + email);
System.out.println("age=" + age);
System.out.println("gapYears=" + gapYears);
System.out.println("eligibility=" + eligibilityPercent + "%");
}
}args에는 명령 뒤의 값이 문자열로 들어옵니다.
따라서 먼저 값이 정확히 세 개인지 확인한 뒤 위치 0, 1, 2를 읽습니다.
Integer.parseInt(문자열)은 "75"처럼 정수 모양인 문자열을 int 값으로 바꿉니다.
지금은 두 숫자 인자가 정수 모양이라고 가정하고, 잘못된 문자열을 다시 입력받는 방법은 ch3의 입력 검증에서 다룹니다.
email=arithmetic@example.com
age=15
gapYears=5
eligibility=75.0%eligibilityPercent 식에서는 비율을 구한 뒤 100을 곱합니다.
age * 100 / minimumAge도 정수 범위 안에서는 계산할 수 있지만 정수 나눗셈과 중간 곱셈 오버플로를 함께 검토해야 합니다.
여기서는 첫 피연산자를 double로 바꿔 소수 진행률을 보존합니다.
최소 가입 나이가 0이면 현재 코드는 실수 나눗셈 결과로 Infinity를 만들 수 있습니다.
정수 나눗셈과 달리 예외가 아닙니다.
0을 허용할지, 오류로 거절할지는 산술 연산자가 아니라 회원가입의 입력 규칙이 결정합니다.
조건문 절에서 그 경계를 닫습니다.
연습 문제
25세, 40세, 50세의 회원 나이를 사용해 다음을 출력하는 MemberAgeMath를 작성하세요.
- 총 회원 나이
- 실수 평균
- 가장 나이 많은 회원과 가장 어린 회원의 나이 차이
- 30세 이상인 회원 수를 아직 조건문 없이 직접 계산한 값
문자열 결합보다 숫자 계산이 먼저 일어나도록 파생 변수를 둡니다.
해설 보기
public final class MemberAgeMath {
public static void main(String[] args) {
int first = 25;
int second = 40;
int third = 50;
int memberCount = 3;
int total = first + second + third;
double average = (double) total / memberCount;
int ageGap = third - first;
int qualifiedMemberCount = 2;
System.out.println("total=" + total);
System.out.println("average=" + average);
System.out.println("ageGap=" + ageGap);
System.out.println("adultMembers=" + qualifiedMemberCount);
}
}total=115
average=38.333333333333336
ageGap=25
adultMembers=2평균의 긴 소수는 double의 실제 계산 결과입니다.
표시 자릿수 반올림과 계산 값 자체는 다른 문제이며 출력 형식을 다룰 때 분리합니다.
다음 절에서는 각 회원이 30세 이상인지 비교해 qualifiedMemberCount를 계산 가능한 조건으로 바꿉니다.