{ }codelper
실수 오차P0359

이진 소수로 딱 떨어지나

컴퓨터의 실수는 이진 소수다. 이진 소수에서 소수점 아래 첫째 자리는 1/2, 둘째 자리는 1/4, 셋째 자리는 1/8을 뜻한다. 예를 들어 십진 소수 0.375는 1/4 + 1/8이므로 이진 소수로 0.011이다. 0.1처럼 이진 소수로 적으면 끝없이 이어지는 수도 있다. 0 이상 1 미만인 십진 소수가 n개 주어진다. 각 수를, 적힌 그대로의 값으로 보고 이진 소수로 바꿔 출력한다. 이진 소수가 끝나는 수이면 자리 수가 20을 넘더라도 마지막 1까지 모두 쓴다. 끝나지 않는 수이면 소수점 아래 20자리까지만 쓰고 바로 뒤에 ...을 붙인다.
반올림하지 않고 자른다. 값이 0이면 0만 쓴다.

입력
첫 줄에 n (1 ≤ n ≤ 1,000)이 주어진다.
이어지는 n줄에 수가 한 줄에 하나씩 주어진다.
수는 0.
뒤에 숫자가 1 ~ 60개 붙은 꼴이다.
끝에 0이 붙어 있을 수 있다
출력
수마다 한 줄씩, 주어진 차례대로 출력한다.
이진 소수는 0.
으로 시작한다
예시 1 · stdin
6
0.5
0.375
0.1
0.500
0.0
0.15
stdout
0.1
0.011
0.00011001100110011001...
0.1
0
0.00100110011001100110...
예시 2 · stdin
5
0.1000000000000000055511151231257827021181583404541015625
0.500000000000000000001
0.00000095367431640625
0.000000476837158203125
0.3
stdout
0.0001100110011001100110011001100110011001100110011001101
0.10000000000000000000...
0.00000000000000000001
0.000000000000000000001
0.01001100110011001100...
main.py
입력(stdin)

실행하면 이 줄들이 차례로 input()·표준 입력으로 들어가요(실행 중에 묻지 않아요).

$ 준비됐어요

코드를 입력해 풀려면 PC에서 열어 주세요. 모바일에서는 문제와 풀이를 볼 수 있어요.

다른 언어로는?언어별 풀이 보기 · 8개 언어 풀이

소수에 2를 곱해서 1을 넘으면 이진 자리가 1, 아니면 0이에요.
0.375는 0.75(0), 1.5(1), 1.0(1)이라 0.011이에요.
풀이는 여덟 언어 모두 수를 문자열로 읽고, 십진 숫자를 한 자리씩 담아 손으로 하듯 2를 곱해요.
맨 위로 넘어간 올림이 다음 이진 자리예요.
함정 하나: 실수로 읽는 순간 이미 다른 수예요.
0.1을 실수로 읽어 0이 될 때까지 2를 곱하면 첫 예시 셋째 줄이 0.0001100110011001100110011001100110011001100110011001101로 55자리에서 끝나 버려요.
실수에 담긴 값이 0.1이 아니라 0.1000000000000000055511151231257827021181583404541015625이기 때문이에요.
둘째 예시 첫 줄이 바로 그 수라서, 그 줄의 답이 55자리예요.
함정 둘: 가까운 수들이 한 실수로 합쳐져요.
둘째 예시의 0.500000000000000000001은 실수로 읽으면 0.5와 같아져서 0.1이 나와요.
Python float, JS Number, C strtod, Java Double.parseDouble, C# double.Parse, 크레이콘 수로변환 모두 이 수를 0.5와 같다고 해요.
함정 셋: 소수점 아래를 정수 하나로 읽어도 안 돼요.
60자리까지라 64비트 정수에 안 담기고, JS Number는 16자리쯤부터 아랫자리를 잃어서 둘째 예시 첫 줄이 0.00011001100110011001...이 돼요.
함정 넷: 20번만 곱해 보고 끝나지 않는다고 하면 안 돼요.
소수점 아래 d자리인 수가 끝난다면 d자리 안에 끝나니 그만큼은 가 봐야 해요.
둘째 예시 넷째 줄은 21자리에서 끝나요.
함정 다섯: 20자리를 채웠다고 끝나지 않는 수는 아니에요.
둘째 예시 셋째 줄은 딱 20자리에서 끝나서 ...이 붙지 않아요.
함정 여섯: 0.0은 이진 자리가 하나도 없어요.
0.이 아니라 0이에요.
실수끼리 더할 때도 같은 일이 생겨요.
0.1 + 0.2 == 0.3은 여덟 언어 모두 거짓이에요.
합을 그냥 출력하면 Python·JS·Java·C#은 0.30000000000000004, C %g와 C++ cout과 크레이콘은 0.3을 보여 줘요.
보이는 것이 같아도 값은 달라요.
정확한 수가 필요하면 Python Fraction("0.1"), Java new BigDecimal("0.1")처럼 문자열로 만들어요.
Fraction(0.1)은 3602879701896397/36028797018963968이고 new BigDecimal(0.1)은 위의 55자리 수예요.
C# decimal은 0.1m + 0.2m == 0.3m이 참이지만 28자리쯤까지만 담아서, 이 문제의 수를 decimal.Parse로 읽으면 그 뒤는 반올림되어 사라져요.

Pythonmain.py · 38 줄
import sys SHOWN = 20  # 딱 떨어지지 않을 때 보여 줄 이진 자리 수  def to_binary(frac):    """소수점 아래 십진 숫자열 frac을 이진 소수로 바꾼다."""    # 실수로 바꾸지 않고 십진 숫자를 하나씩 수로 담는다. 끝의 0은 값에 상관없으니 뗀다    digits = [int(ch) for ch in frac.rstrip("0")]    limit = max(len(digits), SHOWN)  # d자리 소수가 딱 떨어진다면 d번 안에 끝난다    bits = []    while digits and len(bits) < limit:        # 2를 곱한다: 손으로 하듯 맨 끝자리부터 올림을 넘겨 간다        carry = 0        for i in range(len(digits) - 1, -1, -1):            v = digits[i] * 2 + carry            digits[i] = v % 10            carry = v // 10        bits.append(str(carry))  # 소수점 위로 넘어간 올림이 다음 이진 자리다        while digits and digits[-1] == 0:            digits.pop()    if not bits:        return "0"    if not digits:        return "0." + "".join(bits)    return "0." + "".join(bits[:SHOWN]) + "..."  def main():    lines = sys.stdin.read().split()    n = int(lines[0])    out = []    for x in lines[1:1 + n]:        out.append(to_binary(x[2:]))  # 맨 앞의 "0."을 뗀다    print("\n".join(out))  main()
JavaScriptmain.js · 28 줄
const lines = require("fs").readFileSync(0, "utf8").trim().split(/\s+/);const SHOWN = 20; // 딱 떨어지지 않을 때 보여 줄 이진 자리 수 function toBinary(frac) {  // Number로 바꾸지 않고 십진 숫자를 하나씩 수로 담는다. 끝의 0은 값에 상관없으니 뗀다  const digits = [...frac.replace(/0+$/, "")].map(Number);  const limit = Math.max(digits.length, SHOWN); // d자리 소수가 딱 떨어진다면 d번 안에 끝난다  let bits = "";  while (digits.length > 0 && bits.length < limit) {    // 2를 곱한다: 맨 끝자리부터 올림을 넘겨 간다    let carry = 0;    for (let i = digits.length - 1; i >= 0; i--) {      const v = digits[i] * 2 + carry;      digits[i] = v % 10;      carry = Math.floor(v / 10);    }    bits += carry; // 소수점 위로 넘어간 올림이 다음 이진 자리다    while (digits.length > 0 && digits[digits.length - 1] === 0) digits.pop();  }  if (bits === "") return "0";  if (digits.length === 0) return "0." + bits;  return "0." + bits.slice(0, SHOWN) + "...";} const n = Number(lines[0]);const out = [];for (let i = 1; i <= n; i++) out.push(toBinary(lines[i].slice(2))); // 맨 앞의 "0."을 뗀다console.log(out.join("\n"));
TypeScriptmain.ts · 28 줄
const lines: string[] = require("fs").readFileSync(0, "utf8").trim().split(/\s+/);const SHOWN = 20; // 딱 떨어지지 않을 때 보여 줄 이진 자리 수 function toBinary(frac: string): string {  // number로 바꾸지 않고 십진 숫자를 하나씩 수로 담는다. 끝의 0은 값에 상관없으니 뗀다  const digits: number[] = [...frac.replace(/0+$/, "")].map(Number);  const limit = Math.max(digits.length, SHOWN); // d자리 소수가 딱 떨어진다면 d번 안에 끝난다  let bits = "";  while (digits.length > 0 && bits.length < limit) {    // 2를 곱한다: 맨 끝자리부터 올림을 넘겨 간다    let carry = 0;    for (let i = digits.length - 1; i >= 0; i--) {      const v = digits[i] * 2 + carry;      digits[i] = v % 10;      carry = Math.floor(v / 10);    }    bits += carry; // 소수점 위로 넘어간 올림이 다음 이진 자리다    while (digits.length > 0 && digits[digits.length - 1] === 0) digits.pop();  }  if (bits === "") return "0";  if (digits.length === 0) return "0." + bits;  return "0." + bits.slice(0, SHOWN) + "...";} const n: number = Number(lines[0]);const out: string[] = [];for (let i = 1; i <= n; i++) out.push(toBinary(lines[i].slice(2))); // 맨 앞의 "0."을 뗀다console.log(out.join("\n"));
JavaMain.java · 41 줄
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader; public class Main {    static final int SHOWN = 20; // 딱 떨어지지 않을 때 보여 줄 이진 자리 수     static String toBinary(String frac) {        // double이나 long으로 바꾸지 않고 십진 숫자를 하나씩 배열에 담는다        int len = frac.length();        while (len > 0 && frac.charAt(len - 1) == '0') len--; // 끝의 0은 값에 상관없으니 뗀다        int[] digits = new int[len];        for (int i = 0; i < len; i++) digits[i] = frac.charAt(i) - '0';        int limit = Math.max(len, SHOWN); // d자리 소수가 딱 떨어진다면 d번 안에 끝난다        StringBuilder bits = new StringBuilder();        while (len > 0 && bits.length() < limit) {            // 2를 곱한다: 맨 끝자리부터 올림을 넘겨 간다            int carry = 0;            for (int i = len - 1; i >= 0; i--) {                int v = digits[i] * 2 + carry;                digits[i] = v % 10;                carry = v / 10;            }            bits.append(carry); // 소수점 위로 넘어간 올림이 다음 이진 자리다            while (len > 0 && digits[len - 1] == 0) len--;        }        if (bits.length() == 0) return "0";        if (len == 0) return "0." + bits;        return "0." + bits.substring(0, SHOWN) + "...";    }     public static void main(String[] args) throws IOException {        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));        int n = Integer.parseInt(br.readLine().trim());        StringBuilder out = new StringBuilder();        for (int i = 0; i < n; i++) {            out.append(toBinary(br.readLine().trim().substring(2))).append('\n'); // 맨 앞의 "0."을 뗀다        }        System.out.print(out);    }}
C#Main.cs · 45 줄
using System;using System.Text; class Program{    const int Shown = 20; // 딱 떨어지지 않을 때 보여 줄 이진 자리 수     static string ToBinary(string frac)    {        // double이나 decimal로 바꾸지 않고 십진 숫자를 하나씩 배열에 담는다        frac = frac.TrimEnd('0'); // 끝의 0은 값에 상관없으니 뗀다        int len = frac.Length;        int[] digits = new int[len];        for (int i = 0; i < len; i++) digits[i] = frac[i] - '0';        int limit = Math.Max(len, Shown); // d자리 소수가 딱 떨어진다면 d번 안에 끝난다        var bits = new StringBuilder();        while (len > 0 && bits.Length < limit)        {            // 2를 곱한다: 맨 끝자리부터 올림을 넘겨 간다            int carry = 0;            for (int i = len - 1; i >= 0; i--)            {                int v = digits[i] * 2 + carry;                digits[i] = v % 10;                carry = v / 10;            }            bits.Append(carry); // 소수점 위로 넘어간 올림이 다음 이진 자리다            while (len > 0 && digits[len - 1] == 0) len--;        }        if (bits.Length == 0) return "0";        if (len == 0) return "0." + bits;        return "0." + bits.ToString(0, Shown) + "...";    }     static void Main()    {        int n = int.Parse(Console.ReadLine());        var sb = new StringBuilder();        for (int i = 0; i < n; i++)        {            sb.Append(ToBinary(Console.ReadLine().Trim().Substring(2))).Append('\n'); // 맨 앞의 "0."을 뗀다        }        Console.Write(sb);    }}
C++main.cpp · 43 줄
#include <algorithm>#include <iostream>#include <string>#include <vector>using namespace std; const size_t SHOWN = 20; // 딱 떨어지지 않을 때 보여 줄 이진 자리 수 string toBinary(const string& frac) {    // double로 바꾸지 않고 십진 숫자를 하나씩 수로 담는다    vector<int> digits;    for (char ch : frac) digits.push_back(ch - '0');    while (!digits.empty() && digits.back() == 0) digits.pop_back(); // 끝의 0은 값에 상관없으니 뗀다    size_t limit = max(digits.size(), SHOWN); // d자리 소수가 딱 떨어진다면 d번 안에 끝난다    string bits;    while (!digits.empty() && bits.size() < limit) {        // 2를 곱한다: 맨 끝자리부터 올림을 넘겨 간다        int carry = 0;        for (size_t i = digits.size(); i-- > 0;) {            int v = digits[i] * 2 + carry;            digits[i] = v % 10;            carry = v / 10;        }        bits += static_cast<char>('0' + carry); // 소수점 위로 넘어간 올림이 다음 이진 자리다        while (!digits.empty() && digits.back() == 0) digits.pop_back();    }    if (bits.empty()) return "0";    if (digits.empty()) return "0." + bits;    return "0." + bits.substr(0, SHOWN) + "...";} int main() {    ios::sync_with_stdio(false);    cin.tie(nullptr);    int n;    cin >> n;    for (int i = 0; i < n; i++) {        string x;        cin >> x;        cout << toBinary(x.substr(2)) << '\n'; // 맨 앞의 "0."을 뗀다    }    return 0;}
Cmain.c · 40 줄
#include <stdio.h>#include <string.h> #define SHOWN 20 /* 딱 떨어지지 않을 때 보여 줄 이진 자리 수 */ int main(void) {    int n;    if (scanf("%d", &n) != 1) return 0;    for (int t = 0; t < n; t++) {        char x[80];        if (scanf("%79s", x) != 1) return 0; /* double이 아니라 문자열로 읽는다 */        const char *frac = x + 2;            /* 맨 앞의 "0."을 건너뛴다 */        int len = (int)strlen(frac);        while (len > 0 && frac[len - 1] == '0') len--; /* 끝의 0은 값에 상관없으니 뗀다 */        int digits[64];        for (int i = 0; i < len; i++) digits[i] = frac[i] - '0';        int limit = len > SHOWN ? len : SHOWN; /* d자리 소수가 딱 떨어진다면 d번 안에 끝난다 */        char bits[64];        int count = 0;        while (len > 0 && count < limit) {            /* 2를 곱한다: 맨 끝자리부터 올림을 넘겨 간다 */            int carry = 0;            for (int i = len - 1; i >= 0; i--) {                int v = digits[i] * 2 + carry;                digits[i] = v % 10;                carry = v / 10;            }            bits[count++] = (char)('0' + carry); /* 소수점 위로 넘어간 올림이 다음 이진 자리다 */            while (len > 0 && digits[len - 1] == 0) len--;        }        if (count == 0) {            printf("0\n");        } else if (len == 0) {            printf("0.%.*s\n", count, bits);        } else {            printf("0.%.*s...\n", SHOWN, bits);        }    }    return 0;}
Craykonmain.cray · 50 줄
보일자리 = 20; // 딱 떨어지지 않을 때 보여 줄 이진 자리 수 이진으로(소수부){    // 수로 한꺼번에 바꾸지 않고 십진 숫자를 하나씩 목록에 담는다    숫자들 = 새목록(0, 0);    반복 (i = 0; i < 소수부.길이; i++)    {        숫자들.넣기(소수부.부분문자열(i, 1).수로변환());    }    // 끝의 0은 값에 상관없으니 뗀다    동안 (숫자들.개수 > 0 && 숫자들[숫자들.개수 - 1] == 0)    {        숫자들.자리빼기(숫자들.개수 - 1);    }    한도 = 수학.큰수(숫자들.개수, 보일자리); // d자리 소수가 딱 떨어진다면 d번 안에 끝난다    비트 = "";    동안 (숫자들.개수 > 0 && 비트.길이 < 한도)    {        // 2를 곱한다: 맨 끝자리부터 올림을 넘겨 간다        올림 = 0;        반복 (i = 숫자들.개수 - 1; i >= 0; i--)        {            v = 숫자들[i] * 2 + 올림;            숫자들[i] = v % 10;            올림 = 수학.몫(v, 10);        }        비트 = 비트 + 올림; // 소수점 위로 넘어간 올림이 다음 이진 자리다        동안 (숫자들.개수 > 0 && 숫자들[숫자들.개수 - 1] == 0)        {            숫자들.자리빼기(숫자들.개수 - 1);        }    }    만약 (비트 == "")    {        돌려주기 "0";    }    만약 (숫자들.개수 == 0)    {        돌려주기 "0." + 비트;    }    돌려주기 "0." + 비트.부분문자열(0, 보일자리) + "...";} n = 콘솔.줄읽기().수로변환();반복 (t = 0; t < n; t++){    x = 콘솔.줄읽기().공백제거();    콘솔.쓰기(이진으로(x.부분문자열(2))); // 맨 앞의 "0."을 뗀다}