{ }codelper
浮動小数点の誤差P0359

2進小数で割り切れるか

コンピュータの実数は2進小数である。
2進小数では、小数点以下の1桁目は 1/2、2桁目は 1/4、3桁目は 1/8 を表す。
たとえば10進小数 0.375 は 1/4 + 1/8 なので、2進小数では 0.011 である。
0.1 のように、2進小数で書くと終わりなく続く数もある。
0 以上 1 未満の10進小数が n 個与えられる。
それぞれの数を、書かれたとおりの値として2進小数に直して出力せよ。
2進小数が終わる数なら、桁数が 20 を超えても最後の 1 まですべて書く。
終わらない数なら、小数点以下20桁までだけ書き、すぐ後ろに ...
を付ける。
四捨五入せずに切り捨てる。
値が 0 なら 0 だけを書く。

入力
1行目に n (1 ≤ n ≤ 1,000) が与えられる。
続く n 行に数が1行に1つずつ与えられる。
数は 0.
の後ろに数字が 1 ~ 60 個付いた形である。
末尾に 0 が付いていることがある
出力
数ごとに1行ずつ、与えられた順に出力する。
2進小数は 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 以上になれば2進の桁は 1、ならなければ 0 です。
0.375 は 0.75(0)、1.5(1)、1.0(1) なので 0.011 です。
解答は8言語とも数を文字列で読み、10進の数字を1桁ずつ持って、筆算のように 2 を掛けます。
いちばん上からあふれた繰り上がりが次の2進の桁です。
罠その1:実数として読んだ時点で、もう別の数です。
0.1 を実数で読んで 0 になるまで 2 を掛けると、1つ目の例の3行目が 0.0001100110011001100110011001100110011001100110011001101 と55桁で終わってしまいます。
実数に入っている値が 0.1 ではなく 0.1000000000000000055511151231257827021181583404541015625 だからです。
2つ目の例の1行目がまさにその数なので、その行の答えは55桁です。
罠その2:近い数どうしが1つの実数にまとまります。
2つ目の例の 0.500000000000000000001 は実数で読むと 0.5 と等しくなり、0.1 が出ます。
Python float、JS Number、C strtod、Java Double.parseDouble、C# double.Parse、クレイコン 数に のどれも、この数は 0.5 と等しいと答えます。
罠その3:小数点以下を1つの整数として読んでもだめです。
60桁まであるので64ビット整数に入らず、JS Number は16桁あたりから下の桁を失うので、2つ目の例の1行目が 0.00011001100110011001... になります。
罠その4:20回掛けただけで終わらないと決めてはいけません。
小数点以下 d 桁の数が終わるなら d 桁以内に終わるので、そこまでは進める必要があります。
2つ目の例の4行目は21桁で終わります。
罠その5:20桁を埋めたからといって終わらない数とは限りません。
2つ目の例の3行目はちょうど20桁で終わるので ...
は付きません。
罠その6:0.0 には2進の桁が1つもありません。
0. ではなく 0 です。
実数どうしを足すときにも同じことが起きます。
0.1 + 0.2 == 0.3 は8言語とも偽です。
和をそのまま出力すると、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."을 뗀다}