Post

BeakJoon 4344. 평균은 넘겠지 - 평균 이상 학생 비율 계산

python의 round는 사사오입이 아니다. 이 부분을 생각하며 풀자

문제 URL: https://www.acmicpc.net/problem/4344

😒문제


대학생 새내기들의 90%는 자신이 반에서 평균은 넘는다고 생각한다. 당신은 그들에게 슬픈 진실을 알려줘야 한다.

👀제약조건


입력

첫째 줄에는 테스트 케이스의 개수 C가 주어진다.

둘째 줄부터 각 테스트 케이스마다 학생의 수 N(1 ≤ N ≤ 1000, N은 정수)이 첫 수로 주어지고, 이어서 N명의 점수가 주어진다. 점수는 0보다 크거나 같고, 100보다 작거나 같은 정수이다.

출력

각 케이스마다 한 줄씩 평균을 넘는 학생들의 비율을 반올림하여 소수점 셋째 자리까지 출력한다.

예제 입력 1

1
2
3
4
5
6
7
5
5 50 50 70 80 100
7 100 95 90 80 70 60 50
3 70 90 80
3 70 90 81
9 100 99 98 97 96 95 94 93 91

예제 출력 1

1
2
3
4
5
40.000%
57.143%
33.333%
66.667%
55.556%

🤩접근방법


브론즈 문제임에도 불구하고 정답률이 34%로 이렇게 낮은 이유는 파이썬 언어의 반올림 때문입니다.

저도 이 낮은 정답률에 기여를 하였습니다..

Untitled

학생들의 비율을 반올림하여 소수점 셋째 짜리까지 출력해야하기 때문에 round 함수나 출력 포맷팅이 사사오입 방식으로 반올림 해줄 것을 기대하며 문제를 풀게 됩니다.

그러나 값을 확인하면 아래와 같은 결과가 나타납니다.

Untitled 2

?!!
아.. 그동안 반올림을 사사오입으로 생각하여 풀었던 수 많은 코딩테스트 문제들이 떠올랐습니다.

그 문제들은 다 틀렸구나..

round함수가 사사오입의 원칙을 따른다고 되어있는 문서들도 있는데,공식문서에는 아래와 같이 float에 대한 round 동작은 예상과 다를 수 있다고 친절히 가이드해주고 있습니다.

Untitled 3

그럼 round는 함수는 어떻게 동작할까?


round 함수 동작에 앞서 오사오입, 오사육입이란 개념을 먼저 알아보겠습니다. 오사오입/오사육입이란?

오사오입

반올림에서 5 미만의 숫자는 버림하며 5 초과의 숫자는 올림한다.

5의 경우에는 5의 앞자리가 홀수인 경우엔 올림을 하고 짝수인 경우엔 버림을 하여 짝수로 만들어준다.

예를 들어 53.4553.4로, 32.7532.8로 반올림한다.

오사육입

사사오입과는 반대로 5를 버리는 방법이다. 5 초과 올림, 5 미만 내림은 동일하다.

위 개념을 학습하면서 저는 round 함수는 도대체 어떻게 동작하는데? 라는 궁금증이 남아 있었습니다.

구글링 결과 파이썬 round함수는 “오사오입으로 동작한다” 라는 내용을 많이 볼 수 있었는데 그럼 위 오사오입의 예제를 넣어보면 결과값이 기대한 대로 나올까요?

대답은 아니요 입니다

아래의 실행결과를보면 53.45를 파이썬 round함수로 실행하면 53.4가 아닌 53.5로 나옵니다. 5의 앞자리가 짝수이지만 버리지 않고 올려버렸습니다.

Untitled 4

그럼 반대로 “그럼 오사육입이냐?” 라고 물었을 땐 아래 0.55에서 앞자리가 홀수인 경우엔 버려서 0.5가 나오는 것을 기해야하는데 아래 예제처럼 0.6이 나오게 됩니다.

Untitled 5

관련 읽어볼만한 내용은 (링크)를 남겨두겠습니다.

그럼 어떻게 풀어?


다시 문제로 돌아와서 그럼 이 문제를 어떻게 푸느냐가 남았습니다.

다양한 가이드들이 있는데 그 중 하나는 사사오입 방식으로 동작하도록 round 함수에 들어갈 입력값을 조작하는 것입니다.

1
2
def roundTraditional(val,digits):
   return round(val+10**(-len(str(val))-1), digits)

참고 : stack overflow

위 코드는 소수 자릿수보다 작은 값을 반올림하려는 값에 보장된 수보다 작은 값을 더함으로써 반올림할 자릿수보다 하위 자릿수가 5인 경우엔 반올림하고, 4인 경우엔 내림하도록 보장합니다.

다른 방식의 풀이로는 Decimal 객체를 활용한 방법이 있습니다.

1
2
3
4
5
6
from decimal import Decimal, ROUND_HALF_UP

def custom_round(value, digits):
    rounded = Decimal(value).quantize(Decimal('0.' + '0' * digits), rounding=ROUND_HALF_UP)
    return float(rounded)

또 다른 방식 중 하나는 소수점 4번째의 값을 구하여 이 값이 5이상일 경우 소수점 3번째 값을 직접 반올림 하는 방식입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
t = int(input())

for _ in range(t):
    seq = list(map(int,input().split()[1:]))
    avg = sum(seq) / len(seq)
    
    cnt = 0
    for s in seq:
        if s > avg:
            cnt += 1

    percent = cnt / len(seq) * 100
    four = int(percent * (10**4)) % 10
    if four >= 5:
        percent = int(percent*(10**3)+1) / (10**3)
    
    print(f'{percent:.3f}%')

🤔풀이


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def roundTraditional(val, digits):
    return round(val+10**(-len(str(val))-1), digits)

t = int(input())

for _ in range(t):
    seq = list(map(int,input().split()[1:]))
    avg = sum(seq) / len(seq)
    
    cnt = 0
    for s in seq:
        if s > avg:
            cnt += 1

    result = roundTraditional(cnt / len(seq) * 100, 3)
    print(f'{result:.3f}%')

🧐


This post is licensed under CC BY 4.0 by the author.