分数化小数

GitHub跳转原题关系图返回列表

模拟竖式长除法,用字典登记余数第一次出现的位置:余数重复即找到循环节,余数变 0 即有限小数,总步数不超过分母。

OJ: roj

题目 ID: 2039

难度:普及-

标签:模拟数学python

创建: 2026-10-01 04:30

更新: 2026-10-07 13:50

题目描述

输入一个分数 N/DN/D(1⩽N,D⩽1000001 \leqslant N, D \leqslant 100000),输出它的小数形式:循环节用一对圆括号括起来(如 1/31/3 写成 0.(3)),整数写成 xxx.0。输出串长度超过 76 个字符时,每 76 个字符换一行。 样例输入:45 56;样例输出:0.803(571428)。

思路

模拟竖式长除法:每一步的商位由当前余数唯一决定,用数组登记每个余数第一次出现时已写出的小数位数。 余数变 0 就是有限小数(一位没写补 0);某个余数第二次出现时,它第一次出现的位置之后就是循环节,加上括号即可。 余数只有 DD 种取值,O(D)O(D) 步内必然终止。

参考代码

Python 版:

python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-01 04:30
# update_at: 2026-10-01 04:30

import sys


def long_division_digits(n: int, d: int) -> str:
    """模拟 n/d 的长除法:返回整数部分、纯小数部分和循环节起点。

    余数每出现一次就登记它的位数;同一个余数第二次出现的位置
    就是循环节的起点,这保证总步数不超过 d(余数只有 d 种取值)。
    """
    seen: dict[int, int] = {}  # 余数 -> 它出现时已在小数部分写下的位数
    digits: list[str] = []     # 小数点后的数字串
    r = n % d                  # 当前余数,0 表示已经除尽
    while r and r not in seen:
        seen[r] = len(digits)
        r *= 10
        digits.append(str(r // d))
        r %= d

    body = ''.join(digits[: seen[r]]) + '(' + ''.join(digits[seen[r]:]) + ')' if r else ''.join(digits)
    return f'{n // d}.{body or "0"}'


def solve() -> None:
    n, d = map(int, sys.stdin.buffer.read().split())
    text = long_division_digits(n, d)
    print('\n'.join(text[i:i + 76] for i in range(0, len(text), 76)))


if __name__ == "__main__":
    solve()

C++ 版(同一算法):

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-06 10:11
 * update_at: 2026-10-06 10:11
 */
// main.cpp:模拟竖式长除法,把分数 N/D 化成小数并标记循环节。
#include <cstdio>

typedef long long ll;

const int MAXD = 100005;

int seen[MAXD];    // seen[r] = 余数 r 第一次出现时已写出的小数位数(-1 表示还没出现)
char digits[MAXD]; // 小数点后依次写出的每一位数字
ll cnt;            // 已写出的小数位数

ll n, d;

int main() {
    scanf("%lld %lld", &n, &d);
    for (ll i = 0; i < d; i++) seen[i] = -1;

    ll r = n % d; // 长除法当前余数,余数决定之后的所有商位
    ll k = -1;    // 循环节起点:余数重复时等于它第一次出现的位置,-1 表示除尽
    while (r != 0) {
        if (seen[r] != -1) { // 这个余数出现过,之后每一步都会重复
            k = seen[r];
            break;
        }
        seen[r] = cnt;      // 登记余数出现时已写出的位数
        r = r * 10;
        digits[cnt++] = '0' + r / d; // 本位商
        r = r % d;
    }

    // 组装完整输出串:整数部分 + '.' + 小数部分(循环节加括号)
    char ans[MAXD + 30];
    ll len = snprintf(ans, sizeof(ans), "%lld.", n / d);
    if (k == -1) {
        if (cnt == 0) ans[len++] = '0'; // 整数情形必须写成 x.0
        for (ll i = 0; i < cnt; i++) ans[len++] = digits[i];
    } else {
        for (ll i = 0; i < k; i++) ans[len++] = digits[i]; // 不循环部分
        ans[len++] = '(';
        for (ll i = k; i < cnt; i++) ans[len++] = digits[i]; // 循环节
        ans[len++] = ')';
    }
    ans[len] = '\0';

    // 每 76 个字符一行输出(换行符不占 76 个字符的名额)
    for (ll i = 0; i < len; i++) {
        putchar(ans[i]);
        if ((i + 1) % 76 == 0) putchar('\n');
    }
    putchar('\n');
    return 0;
}