{ }codelper
浮点精度P0359

二进制小数能除尽吗

计算机里的实数是二进制小数。
在二进制小数中,小数点后第 1 位表示 1/2,第 2 位表示 1/4,第 3 位表示 1/8。
例如十进制小数 0.375 等于 1/4 + 1/8,所以写成二进制小数是 0.011。
也有像 0.1 这样,写成二进制小数就没有尽头的数。
给出 n 个大于等于 0 且小于 1 的十进制小数。
把每个数按写出来的值原样看待,转换成二进制小数并输出。
如果二进制小数有尽头,即使位数超过 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()/标准输入传入(运行中不会等待输入)。

$ 就绪

请在电脑上打开以编写和运行代码。在手机上可以查看题目和题解。

其他语言怎么写?查看各语言题解 · 8 种语言题解

把小数乘以 2,达到 1 则这一位二进制是 1,否则是 0。
0.375 依次是 0.75(0)、1.5(1)、1.0(1),所以是 0.011。
八种语言的解答都把数当作字符串读入,把十进制数字一位一位存起来,像笔算那样乘以 2。
从最高位溢出的进位就是下一位二进制。
陷阱一:一旦读成浮点数,它就已经是另一个数了。
把 0.1 读成浮点数再不断乘 2 直到变成 0,第一个例子的第 3 行会变成 0.0001100110011001100110011001100110011001100110011001101,在第 55 位结束。
因为浮点数里存的不是 0.1,而是 0.1000000000000000055511151231257827021181583404541015625。
第二个例子的第 1 行正是这个数,所以那一行的答案有 55 位。
陷阱二:相近的数会合并成同一个浮点数。
第二个例子里的 0.500000000000000000001 读成浮点数后与 0.5 相等,于是输出 0.1。
Python float、JS Number、C strtod、Java Double.parseDouble、C# double.Parse、Craykon 转数字 都认为这个数等于 0.5。
陷阱三:把小数点后的部分读成一个整数也不行。
最多有 60 位,64 位整数放不下,而 JS Number 从 16 位左右开始丢失低位,第二个例子的第 1 行会变成 0.00011001100110011001...。
陷阱四:不能只乘 20 次就断定没有尽头。
小数点后有 d 位的数如果有尽头,一定在 d 位以内结束,所以必须算到那里。
第二个例子的第 4 行在第 21 位结束。
陷阱五:填满 20 位不代表没有尽头。
第二个例子的第 3 行恰好在第 20 位结束,所以不加 ...。
陷阱六:0.0 一位二进制数字也没有。
应输出 0,而不是 0.。
浮点数相加时也会发生同样的事。
0.1 + 0.2 == 0.3 在八种语言里都是假。
直接输出这个和,Python、JS、Java、C# 显示 0.30000000000000004,C %g、C++ cout 和 Craykon 显示 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."을 뗀다}