https://www.acmicpc.net/problem/9333
9333번: 돈 갚기
각 테스트 케이스 마다, 돈을 다 갚는데 몇 달이 걸리는지를 출력한다. 만약, 1200달이 넘어도 돈을 갚을 수 없다면, impossible을 출력한다.
www.acmicpc.net
문제
상근이는 선영이에게 B달러를 빌렸다.
이제 돈을 갚을 시간이다. 매월 초에 상근이가 내야하는 금액의 R 퍼센트가 이자로 붙는다. 상근이는 매월 말에 과외비 M달러를 받고, 이 금액 만큼 선영이에게 갚을 수 있다.
상근이는 선영이에게 더 이상 돈을 빌리지 않고, 상근이는 과외 이외의 일을 하지 않으며, 과외비는 인상되지 않는다.
이러한 경우에 상근이가 돈을 다 갚는데 몇 달이 걸리는지 구하는 프로그램을 작성하시오.
이자는 가까운 센트로 반올림한다. 100센트는 1달러이고, 0.5센트보다 크거나 같은 경우에 1센트로 올림, 나머지 경우에 버림한다.
입력
입력은 여러 개의 테스트 케이스로 이루어져 있다. 첫째 줄에는 테스트 케이스의 수가 주어지며, 1000을 넘지 않는다.
각 테스트 케이스는 한 줄로 이루어져 있고, R, B, M이 주어진다. 세 숫자는 모두 실수로 소수점 둘째 자리까지 주어지며, R ≤ 50.00, B, M ≤ 50000.00을 만족한다.
출력
각 테스트 케이스 마다, 돈을 다 갚는데 몇 달이 걸리는지를 출력한다. 만약, 1200달이 넘어도 돈을 갚을 수 없다면, impossible을 출력한다.
(23.03.10)
교내 대회를 준비하기 위해 랜실디를 짬짬히 시간 내서 하는 중이다. 이 문제는 단순한 수학문제인 것 같은데, 참 이것저것 문제가 발생한다.
1. 부동소수점 오차 문제: 입력이 소수로 들어오기 때문에 부동소수점 오차가 발생한다. 원금 b와 소득 m의 경우는 소수점이 센트를 의미하기 때문에, 100을 곱해서 정수로 처리했지만, 이자는 소수로 처리하고 있기 때문에, 여기서 문제가 발생하는 것 같다.
2. (해결) 갚아야할 상환금이 int형을 벗어나는 경우가 존재한다.
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
void sol(int b, int m, double r) {
long long rest = b;
for (int i = 1; i <= 1200; i++) {
rest += (long long)(rest * r / 100 + 0.5);
rest -= m;
if (rest <= 0) {
cout << i << endl;
return;
}
}
cout << "impossible" << endl;
}
int main () {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int tc;
double b, r, m; // 원금, 이자, 소득
cin >> tc;
while(tc--) {
cin >> r >> b >> m;
sol(100 * b, 100 * m, r);
}
}
일단은 다른 할일 하러 실패한 기록을 적는다.

(23.03.13)
r = 50, b = 50000, m = 100 이하의 수에서 impossible이 아닌 70이 출력되는 현상을 발견했다. int형을 벗어나 long long형을 사용했는데, 이 값 역시 벗어났다. unsigned long long으로 바꾸었다. 그러더니 "틀렸습니다" 가 아닌 "출력 초과"가 발생했다.
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
#define FAIL "impossible"
void sol(int b, const int m, double r) {
unsigned long long rest = b;
for (int i = 1; i <= 1200; i++) {
rest += (unsigned long long)(rest * r / 100 + 0.5); // 이자 곱하고 반올림
rest -= m; // 상환
if (rest <= 0) {
cout << i << endl; // 다 갚았으면 끝
return;
}
}
cout << FAIL << endl; // 반복문 안에서 return이 안됐으면 실패
}
int main () {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int tc;
double b, r, m; // 원금, 이자, 소득
cin >> tc;
while(tc--) {
cin >> r >> b >> m;
sol(100 * b, 100 * m, r);
}
}

(26.02.27)
위 글을 쓴 이후로 부동소수점은 PS의 해악이라는 것을 깨닫게 되고, int형으로 변환하여 계산해야함은 깨달았다. 하지만 원래라면 소수로 계산해야하는 것을 모두 100을 곱하는데, 문제는 이자율 R도 100을 곱해야 하면서 계산식의 변환에 난항을 겪었다. 지금은 편입공부와 병행하며, 편입시험에 정보과목도 같이 있기에 감이나 좀 살릴겸 다시 문제를 조금씩 풀고 있었던 와중에, 해당 글을 쓰는 시점으로 약 2달 전에 달아주신 댓글을 보고 다시 떠올라 식 변환에 도전했고, 생각보다 쉽게 변형했다. 뭔 고생을 한걸까?
문제를 보면, 이자에 이자가 적용되는 복리임을 알 수 있다. 양심이 없는 채권자다. 그러면 월초에 이자가 (복리) 적용되고, 월 말에 과외비를 받은 만큼 전부 상환하니까 문제대로 식을 적으면 원래는 다음과 같은 식일 것이다.

소수점 두자리까지 입력으로 들어오기에 우리는 입력으로 들어오는 R,B,M에 100을 곱하여 자연수로 변환한다. 여기까진 별 문제가 없으나, 이자율 R에도 100을 곱해야하며, 그 이후의 소수점에도 반올림을 적용해야한다. R,B,M에 100을 곱해 소수점을 없앤 값을 r,b,m이라 하고, 문제 조건의 소수점 센트를 안보기 위해선 식을 다음과 같이 바꿀 수 있다.

또한, 처음 상환하는 달에 이자가 과외비보다 같거나 많으면 평생 이자만 갚다가 원금 상환은 꿈도 못꾸고 끝날 것이기에(우리 인생처럼이라는 심한말은 ㄴㄴ), 해당 케이스는 먼저 걸러내도록 하자. 이자는 위 식에서 1+r 대신 r만 넣으면 된다.
#include <bits/stdc++.h>
#define endl '\n'
#define FAIL "impossible"
using namespace std;
using ll = long long;
ll r, b, m;
void input() {
int r1, r2, b1, b2, m1, m2;
scanf("%d.%d %d.%d %d.%d", &r1, &r2, &b1, &b2, &m1, &m2);
r = r1 * 100 + r2; b = b1 * 100 + b2; m = m1 * 100 + m2;
}
void sol() {
if (b * r / 10000 >= m) {// 이자가 납부금보다 같거나 많으면 이자에 허덕이며 못갚는다.
cout << FAIL << endl;
return;
}
int month = 0;
while(++month <= 1200) {
b = (b * (10000 + r) + 5000) / 10000; // 이자 곱하고 반올림
b -= m; // 상환
if (b <= 0) {
cout << month << endl; // 다 갚았으면 끝
return;
}
}
cout << FAIL << endl; // 반복문 안에서 return이 안됐으면 이자만 갚다가 죽었다.
}
int main () {
cin.tie(0)->sync_with_stdio(false);
int tc;
scanf("%d", &tc);
while(tc--) {
input();
sol();
}
}

길고 긴 악연을 끝냈다.
'coding_test > BAEKJOON' 카테고리의 다른 글
| 백준 2877번 C++ 풀이 (0) | 2023.04.25 |
|---|---|
| 백준 2491번 C++ 풀이 (0) | 2023.03.28 |
| 백준 6800번 C++ 풀이 (0) | 2023.03.07 |
| 백준 21920번 C++ 풀이 (1) | 2023.02.05 |
| 백준 3005번 C++ 풀이 (1) | 2023.02.05 |