백준 19532번 문제를 풀다가 발견한 이슈를 정리합니다.
브론즈 II, 시간 제한 1초(추가 시간 없음)
수현이는 4차 산업혁명 시대에 살고 있는 중학생이다. 코로나 19로 인해, 수현이는 버추얼 학교로 버추얼 출석해 버추얼 강의를 듣고 있다. 수현이의 버추얼 선생님은 문자가 2개인 연립방정식을 해결하는 방법에 대해 강의하고, 다음과 같은 문제를 숙제로 냈다.
다음 연립방정식에서n와 의 값을 계산하시오.

4차 산업혁명 시대에 숙제나 하고 앉아있는 것보다 버추얼 친구들을 만나러 가는 게 더 가치있는 일이라고 생각했던 수현이는 이런 연립방정식을 풀 시간이 없었다. 다행히도, 버추얼 강의의 숙제 제출은 인터넷 창의 빈 칸에 수들을 입력하는 식이다. 각 칸에는 이상 이하의 정수만 입력할 수 있다. 수현이가 버추얼 친구들을 만나러 버추얼 세계로 떠날 수 있게 도와주자.
정수 , , , , , 가 공백으로 구분되어 차례대로 주어진다. () 문제에서 언급한 방정식을 만족하는 가 유일하게 존재하고, 이 때 와 가 각각 이상 이하의 정수인 경우만 입력으로 주어짐이 보장된다.
문제의 답인 와 를 공백으로 구분해 출력한다.
입력 1
1 3 -1 4 1 7
출력 1
2 -1
입력 2
2 5 8 3 -4 -11
출력 2
-1 2
이 문제를 처음 봤을때는
ㅋㅋ 쉽네 하고 아래와 같이 풀었다.
a, b, c, d, e, f = map(int, input().split(' '))
y = int((c/a - f/d)/(b/a-e/d))
x = int(c/a - (b/a)*y)
print(x, y)
그런데 결과가.. 2%에서 틀렸다고 뜸
어째서?

그래서 반례를 열심히 찾아봤다.

내 코드에서도 1 1 3 7 6 1을 입력하면 -16 19으로 틀리게 출력하는 것을 알게됨.
아니 부동소수점 오차이거먼데 옛날에 들었던것 같은디
ref : https://ko.wikipedia.org/wiki/%EB%B6%80%EB%8F%99%EC%86%8C%EC%88%98%EC%A0%90
실수를 컴퓨터상에서 근사하여 표현할 때 소수점의 위치를 고정하지 않고 그 위치를 나타내는 수를 따로 적는 것으로, 유효숫자를 나타내는 가수(假數)와 소수점의 위치를 풀이하는 지수(指數)로 나누어 표현한다.
ref : https://ko.wikipedia.org/wiki/IEEE_754

예시
−118.625(십진법)를 IEEE 754(32비트 단정밀도)로 표현해 보자.
부동소수점 표기 방식을 자세히 살펴보면, 2진수로 소숫점을 나타내는 것을 확인할 수 있다.
당연히 컴퓨터는 2진수를 사용하니까~라고 생각하면서 넘겼었는데,
사람은 10진수를 사용하는게 문제였다.
ref : https://docs.python.org/ko/3.13/tutorial/floatingpoint.html
오늘날 대부분 기계에서, float는 이진 분수로 근사 되는 데, 최상위 비트로부터 시작하는 53비트를 분자로 사용하고, 2의 거듭제곱 수를 분모로 사용합니다. 1/10의 경우, 이진 분수는 3602879701896397 / 2 ** 55 인데, 실제 값 1/10과 거의 같지만 정확히 같지는 않습니다.
많은 사용자는 값이 표시되는 방식 때문에 근사를 인식하지 못합니다. 파이썬은 기계에 저장된 이진 근삿값의 진짜 십진 값에 대한 십진 근삿값을 인쇄할 뿐입니다. 대부분 기계에서, 만약 파이썬이 0.1로 저장된 이진 근삿값의 진짜 십진 값을 출력한다면 다음과 같이 표시해야 합니다:
>>> 0.1
0.1000000000000000055511151231257827021181583404541015625
이것은 대부분 사람이 유용하다고 생각하는 것보다 많은 숫자이므로, 파이썬은 반올림된 값을 대신 표시하여 숫자를 다룰만하게 만듭니다:
>>> 1 / 10
0.1
인쇄된 결과가 정확히 1/10인 것처럼 보여도, 실제 저장된 값은 가장 가까운 표현 가능한 이진 소수임을 기억하세요.
하나의 환상은 다른 환상을 낳을 수 있습니다. 예를 들어, 0.1은 정확히 1/10이 아니므로, 0.1의 세 개를 합한 것 역시 정확히 0.3이 아닙니다:
>>> 0.1 + 0.1 + 0.1 == 0.3
False
또한, 0.1은 1/10의 정확한 값에 더 가까워질 수 없고, 0.3도 3/10의 정확한 값에 더 가까워질 수 없으므로, round() 함수로 미리 반올림하는 것은 도움이 되지 않습니다:
>>> round(0.1, 1) + round(0.1, 1) + round(0.1, 1) == round(0.3, 1)
False
숫자를 의도한 정확한 값에 더 가깝게 만들 수는 없지만, math.isclose() 함수는 부정확한 값을 비교할 때 유용할 수 있습니다:
>>> math.isclose(0.1 + 0.1 + 0.1, 0.3)
True
사람은 10진수로 사용해서 자연스럽게 딱 떨어지는 소숫점을 기대한 방면, 컴퓨터는 2진수를 사용하기때문에 10진수로 떨어지는 소숫점을 정확하기 표현하기 어려움
=> 이 문제로 인해 백준 19532번에서 틀린것!
정말로 그런지 코드에서 int를 빼고 확인해보자
code :
a, b, c, d, e, f = map(int, input().split(' '))
y = ((c/a - f/d)/(b/a-e/d))
x = (c/a - (b/a)*y)
print(x, y)
input:
1 1 3 7 6 1
expected output:
-17 20
received output:
-16.999999999999993 19.999999999999993
- 중간에 실수 나누기를 사용하면서 부동소숫점 연산 근사 오차 발생
- 이를
int를 붙여서 단순 절사해서 문제였던 것!- 그리고 이런식으로 풀면,
a==0 or b==0인경우ZeroDivisionError가 발생할 수 있다.
이를 고려해서 코드를 고쳤다.
code :
a, b, c, d, e, f = map(int, input().split(' '))
y = int((c*d - a*f)/(b*d-a*e))
x = int((c*e-b*f)/(a*e-b*d))
print(x, y)
해설 :

통과! 🥳
처음에는 가볍게 접근했다가 머리 끙끙 싸맸다...
그래도 푸니까 뿌듯!