# [Python for C/I] 소수점 다루기

## 문제

야구에서 **타수**는 선수가 공을 치기 위해 타석에 선 횟수이고 **안타**는 공을 잘 쳐서 출루한 횟수입니다.

**타율**은 안타를 타수로 나눈 비율로 만약 8타수 5안타라면 `5/8 = 0.625`로 야구에서는 `6할 2푼 5리`라고 읽습니다.

야구선수는 타율이 가장 높은 선수에게 주어지는 상인 타격왕이 되고 싶어 합니다.

야구선수의 현재 타율은 16타수 6안타로 `3할 7푼 5리`입니다.

이후의 타수와 안타가 입력으로 주어질 때 야구선수의 타율을 할푼리로 출력해 보세요.

단, `0.101`처럼 중간에 0이 들어가 있는 경우 0은 생략하여 `1할 1리`라고 출력하며 타율이 `0.3678`인 경우에는 소수점 넷째 자리 이후는 버림을 하여 `3할 6푼 7리`로 출력합니다.

**입력 예시**

```python
3
2
```

**출력 예시**

```python
4할
2푼
1리
```

**입력 예시 2**

```python
5
2
```

**출력 예시 2**

```python
3할
8푼
```

## **지시사항**

1. 0 이상의 정수인 타수와 안타를 입력받으세요.
    
2. 16타수 6안타인 상태에서 입력받은 타수와 안타를 더한 타율을 구하세요.
    
3. 구한 타율을 할푼리로 출력하세요. 할푼리 아래의 소수점은 모두 버림하며 값이 0인 경우 생략합니다.
    

## 오답

```python
# 1번을 해보세요.
a = int(input())
b = int(input())

# 2번을 해보세요.
result = str((6+b)/(16+a))[0:5]
hal = result[2]
pun = result[3]
li = result[4]

# 3번을 해보세요.
if hal != '0':
    print(f'{hal}할')
if pun != '0':
    print(f'{pun}푼')
if li != '0':
    print(f'{li}리')
```

* **틀린 이유:** 문자열 슬라이싱 방식에 의존했기 때문
    
    1. **고정 길이 가정 문제**
        
        * 위 코드에서는 `(6+b)/(16+a)`의 결과를 문자열로 변환한 후, \[0:5\]로 슬라이싱해 항상 "[0.xxx](http://0.xxx)" 형식(예: "0.421")이라고 가정한다.
            
        * 하지만 타율이 1.0과 같이 정수 부분이 0이 아닌 경우나, 소수점 이하 자릿수가 부족한 경우(예: "1.0" 또는 "0.1")에는 예상한 인덱스(2, 3, 4번)가 존재하지 않아 인덱스 에러가 발생할 수 있다.
            
    2. **정확한 버림 처리 미흡**
        
        * 문제에서는 소수점 넷째 자리 이후를 모두 버림해야 한다. 단순히 문자열 슬라이싱으로 해결하려 하면 부동소수점의 표현 방식에 따라 올바른 자리수가 나오지 않을 위험이 있다.
            
        * 예를 들어, 0.3678를 슬라이싱하면 "0.367"이 맞을 수도 있지만, 부동소수점 연산의 특성상 예상치 못한 오차가 발생할 가능성이 있다.
            

## 정답

```python
# 1번을 해보세요.
a = int(input())
b = int(input())

# 2번을 해보세요.
total = (6+b)/(16+a)

hal = int(total*10)% 10
pun = int(total*100)% 10
li = int(total*1000)% 10

# 3번을 해보세요.
if hal != 0:
    print(f'{hal}할')
if pun != 0:
    print(f'{pun}푼')
if li != 0:
    print(f'{li}리')
```

1. **더 안정적인 방식 사용**
    
    * 할, 푼, 리를 각각 구할 때, 소수점에 10, 100, 1000을 곱한 후 정수 변환과 모듈러 연산을 사용하는 방식이 훨씬 안정적이다.
        
        * 예를 들어,
            
            * 할: `int(타율 * 10) % 10`
                
            * 푼: `int(타율 * 100) % 10`
                
            * 리: `int(타율 * 1000) % 10`  
                이런 방식은 계산 결과가 항상 원하는 자릿수를 정확하게 얻을 수 있도록 보장해 준다.
                
2. **할,푼,리를 구할 때, 각각 소수점에 10, 100, 1000을 곱하는 이유**
    
    * 타율에 10을 곱하면 소수점 오른쪽 첫째 자리가 정수 부분으로 옮겨진다. 예를 들어, 타율이 0.3678이라면, 0.3678 × 10 = 3.678이 되고, 여기에 `int()`를 적용하면 3이 된다. 이 3이 바로 "할"에 해당한다.
        
    * 같은 방식으로 타율에 100을 곱하면 0.3678 × 100 = 36.78이 되고, `int()`를 취하면 36이 된다. 여기서 36을 10으로 나눈 나머지(즉, 36 % 10)는 6이 되어 "푼"이 된다.
        
    * 타율에 1000을 곱하면 0.3678 × 1000 = 367.8이 되고, `int()`를 취하면 367이 되며, 이 값을 10으로 나눈 나머지(367 % 10)는 7이 되어 "리"가 된다.
        
    * 이렇게 하면 문자열 변환이나 슬라이싱에 의존하지 않고도, 숫자 연산만으로 각 자릿수를 정확하게 추출할 수 있다.
        

## 다른 방법

* **소수점 넷째 자리 이후 버림하는 방법**
    
    * 문제에서는 소수점 넷째 자리 이후를 버림(자르기) 해야 하므로, 타율에 1000을 곱한 후 `int()` 함수를 사용하면 소수점 이하의 자릿수가 모두 제거된다.
        
    * 예를 들어, 타율 0.3678의 경우 `int(0.3678 * 1000)`은 367이 된다. 이후 367을 다시 1000으로 나누면 0.367이 되어, 넷째 자리 이후가 버림 처리된 결과를 얻을 수 있다.
        
    * 즉, `truncated_rate = int(타율 * 1000) / 1000`와 같이 하면 원하는 자리수까지만 남기고 나머지는 버릴 수 있다.
        

## 질문 정리

### Q1: 정수 연산을 통해 자릿수를 추출할 때 발생할 수 있는 부동소수점 오차 문제는 어떻게 관리할 수 있을까요?

A1: 정수 연산을 통한 자릿수 추출 시, 이러한 오차를 관리하기 위해 아래와 같은 방법들을 사용할 수 있다.

* **내장 함수 사용:**  
    **math.floor()**나 **math.trunc()** 같은 함수를 사용하면, 부동소수점 연산 후 발생하는 미세한 오차를 제거하고 원하는 자릿수까지 버림(truncation) 처리를 할 수 있다. 예를 들어, `math.floor(타율 * 1000) / 1000`를 사용하면 0.3678의 경우 367.8에서 내림 처리된 367을 얻어 0.367로 정확히 버림할 수 있다.
    
* **Decimal 모듈 활용:**  
    Python의 Decimal 모듈은 십진수 기반의 정밀한 계산을 지원한다. 이를 통해 원하는 자리수로 정확하게 계산할 수 있으며, 오차 없이 소수점 이하 자릿수를 관리할 수 있다. 예를 들어,
    
    ```python
    from decimal import Decimal, ROUND_DOWN
    rate = Decimal('0.3678')
    truncated_rate = rate.quantize(Decimal('0.001'), rounding=ROUND_DOWN)
    ```
    
    이렇게 하면 소수점 넷째 자리 이후를 정확하게 버림 처리할 수 있다.
    
* **오차 보정(epsilon) 사용:**  
    경우에 따라 아주 작은 오차를 보정하기 위해 미세한 상수(epsilon)를 더하거나 빼서 오차를 상쇄할 수 있으나, 이 방법은 상황에 따라 복잡해질 수 있어 보통 내장 함수나 Decimal 모듈 사용을 권장한다.
    

### Q2: 다른 방법으로 소수점 이하를 자르는 방법이 있을까요?

A2: 소수점 이하를 자르는 다른 방법은 여러 가지가 있다.

1. **math.floor() 또는 math.trunc() 사용:**
    
    * math.floor 함수는 소수점 이하를 완전히 버림해 정수만 남기는 함수이다.
        
    * `truncated_rate = math.floor(타율 * 1000) / 1000`  
        이 방법은 소수점 넷째 자리 이후를 버림 처리해 정확한 결과를 제공한다.
        
    * 예를 들어, 타율이 0.3678이라면, 0.3678 × 1000 = 367.8이 되고, math.floor(367.8)는 367이 된다. 이후, 다시 1000으로 나누면 367/1000 = 0.367이 되어, 소수점 넷째 자리 이후가 버림 처리된 결과를 얻을 수 있다.
        
2. **Decimal 모듈 사용:**
    
    * 예:
        
        ```python
        from decimal import Decimal, ROUND_DOWN
        rate = Decimal(str(타율))
        truncated_rate = rate.quantize(Decimal('0.001'), rounding=ROUND_DOWN)
        ```
        
        이 방법은 특히 금융 계산이나 정밀한 소수 계산이 필요한 경우에 유용하다.
        
3. **문자열 포맷팅:**
    
    * 예: `formatted_rate = f"{타율:.3f}"`  
        이 방법은 소수점 이하 세 자리까지 포맷팅한 문자열을 생성하는데, 이 문자열을 그대로 사용하거나 다시 실수형으로 변환할 수 있다.
        

각 방법은 상황에 따라 선택할 수 있으며, 계산의 정밀도와 코드의 가독성 측면에서 고려해 가장 적합한 방법을 사용하면 된다.
