C++
 Computer >> コンピューター >  >> プログラミング >> C++

C++で分数を小数文字列に変換する方法(循環小数への対応)

2つの整数(分数の分子と分母)が与えられたとき、その分数を文字列形式の小数に変換することを考えます。小数部分に循環する桁(循環節)がある場合は、その繰り返し部分を括弧 () で囲んで表現します。例えば、分子が 2、分母が 3 の場合、出力は "0.(6)" となります。

この問題は、筆算による割り算の処理をシミュレートしながら、余りの出現履歴を記録することで解くことができます。以下の手順に従って実装していきましょう。

アルゴリズム

  • 分子が 0 の場合は、そのまま "0" を返します。
  • 結果を格納するための配列 ans を定義します。
  • 符号の判定を行います。分子が負かつ分母が正、または分子が正かつ分母が負の場合は、マイナス記号 '-' を ans に追加します。
  • divisor := |分子|、dividend := |分母| とし、remainder := divisor mod dividend を計算します。
  • x := 整数部(divisor / dividend)を文字列化したものとします。
  • x の各文字を ans に追加します。
  • 余りが 0 であれば、ここで ans を文字列として返します(割り切れたケース)。
  • 小数点 '.' を ans に追加します。
  • 余りの出現位置を記録するためのマップ m を定義します。
  • 余りが 0 になるまで以下を繰り返します。
    • もし同じ余りがマップ m にすでに存在する場合:
      • m[remainder] の示すインデックス位置に開き括弧 '(' を挿入します。
      • ans の末尾に閉じ括弧 ')' を追加します。
      • ループを抜けます。
    • 存在しない場合:
      • m[remainder] := 現在の ans のサイズ(出現位置を記録)
      • remainder := remainder × 10
      • (remainder / dividend) の商に '0' を加えた文字を ans に追加します。
      • remainder := remainder mod dividend
  • 最終的に ans を文字列として返します。

このアルゴリズムのポイントは、同じ余りが2回現れた時点で小数が循環していると判断できることです。マップで各余りが最初に出現した位置を記録しておけば、循環の始まりに正確に括弧を挿入できます。

実装例

それでは、実際のC++コードを見て理解を深めましょう。

#include <bits/stdc++.h>
using namespace std;
class Solution {
    public:
    string fractionToDecimal(int numerator, int denominator) {
        if(numerator == 0)return "0";
        vector <char> ans;
        if(numerator < 0 && denominator > 0 || numerator > 0 && denominator < 0)ans.push_back('-');
        long divisor = labs(numerator);
        long dividend = labs(denominator);
        long remainder = divisor % dividend;
        string x = to_string(divisor/dividend);
        for(int i = 0; i < x.size(); i++){
            ans.push_back(x[i]);
        }
        if(remainder == 0){
            return string(ans.begin(), ans.end());
        }
        ans.push_back('.');
        map <int, int> m;
        while(remainder != 0){
            if(m.find(remainder)!=m.end()){
                ans.insert(ans.begin() + m[remainder], '(');
                ans.push_back(')');
                break;
            }else{
                m[remainder] = ans.size();
                remainder *= 10;
                ans.push_back((remainder / dividend) + '0');
                remainder %= dividend;
            }
        }
        return string(ans.begin(), ans.end());
    }
};
main(){
    Solution ob;
    cout << ((ob.fractionToDecimal(100,6)));
}

入力

100
6

出力

16.(6)

補足:実装上の注意点

この実装では、分子・分母の絶対値を long 型で扱っている点に注目してください。INT_MIN(-2147483648)のような極端な値を int のまま絶対値変換するとオーバーフローが発生するため、labs() を使って long 型に拡張してから計算しています。また、循環の検出には map を使用しているため、時間計算量は O(log n)、空間計算量も O(log n) 程度に収まります(n は分母の値)。筆算の仕組みをそのままコードに落とし込んだシンプルかつ堅牢なアプローチです。

  1. C++で16進数を10進数に変換するプログラムの作成方法

    16進数が入力として与えられたとき、その16進数を10進数に変換するのが本記事のテーマです。 コンピュータにおける16進数は基数16で表現され、10進数は基数10で表現されます。10進数では0〜9の数字のみを使用しますが、16進数では0〜15までの値を扱うことができ、10以上の値はそれぞれA(10)、B(11)、C(12)、D(13)、E(14)、F(15)という英字で表されます。 16進数から10進数への変換手順 16進数を10進数に変換するには、以下の手順に従います。 右端の桁から順に数字を取り出し、0から始まるべき乗を掛けていきます。指数は「桁数 − 1」になるまで1ずつ増やします。

  2. C++で2進数を10進数に変換するプログラムの作り方

    2進数が入力として与えられたとき、その2進数を10進数へ変換するのが本記事のテーマです。 コンピュータにおける10進数は基数10で表現されます。一方、2進数は基数2で表現され、使用するのは0と1という2つの数字だけです。それに対して10進数では、0から9までの任意の数字を扱うことができます。 2進数を10進数に変換するには、右端の桁から順に各桁の数字を取り出し、2のべき乗(0乗から始まり、桁数-1乗まで1ずつ増加)を掛け合わせます。そして、その掛け算の結果をすべて足し合わせることで、最終的な10進数の値が求まります。 以下は、2進数を10進数に変換する流れを図で表したものです。 具体例 入