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

C++で有理数を10進数表記の文字列に変換する方法(循環小数対応)

問題概要

分子(numerator)と分母(denominator)の2つの整数で、有理数「分子 / 分母」を表すことを考えます。この値を10進数表記の文字列として求めます。小数部分に循環する数字(循環節)が含まれる場合は、その部分を括弧 ( ) で囲んで表現します。

たとえば、入力が numerator = 164、denominator = 3 の場合、164 ÷ 3 = 54.666… となるため、出力は「54.(6)」となります。

アルゴリズムの考え方

この問題は、筆算の割り算をコンピュータ上でシミュレートすることで解決できます。手順は以下の通りです。

  • 分子が0の場合は、そのまま「0」を返します。
  • 結果を格納するための配列 ans を用意します。
  • 分子と分母の符号が異なる場合(片方が負でもう片方が正の場合)は、ans の末尾に '-' を追加します。
  • divisor := |分子|、dividend := |分母| とします。
  • remainder := divisor mod dividend として余りを求めます。
  • x := (divisor ÷ dividend) を文字列に変換し、その各文字を ans に追加します(これが整数部分になります)。
  • remainder が0の場合(割り切れた場合)は、ここで ans を文字列として返して終了です。
  • 割り切れない場合は、ans の末尾に '.' を追加します。
  • 余りの出現位置を記録するためのマップ m を定義します。
  • remainder が0になるまで、次の処理を繰り返します。
    • remainder がすでに m に登録されている場合:同じ余りが再び現れたということは、そこから数字の並びが循環していることを意味します。したがって、m[remainder] に記録された位置に '(' を挿入し、ans の末尾に ')' を追加してループを抜けます。
    • まだ登録されていない場合:m[remainder] := 現在の ans のサイズを記録し、remainder := remainder × 10 として、(remainder ÷ dividend) の商の数字を ans の末尾に追加します。その後、remainder := remainder mod dividend と更新します。
  • 最後に ans を文字列として返します。

なお、余りは必ず 0 以上 dividend 未満の範囲に収まるため、同じ余りが再び現れるか、余りが0になって終了するかのどちらかが必ず起こります。つまり、このアルゴリズムは必ず有限回のループで停止することが保証されています。

実装例

理解を深めるために、以下のC++による実装を見てみましょう。

#include <bits/stdc++.h>
using namespace std;
class Solution {
   public:
   string solve(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());
   }
};
string solve(int numerator, int denominator) {
   return (new Solution())->solve(numerator, denominator);
}
int main() {
   cout << solve(164, 3);
}

実装のポイントとして、絶対値の計算に labs() を使用しています。これは int 型の最小値(INT_MIN)を扱う際にオーバーフローを避けるためで、long 型にキャストしてから絶対値を取ることで安全に処理できます。

入力

164, 3

出力

54.(6)
  1. C++で10進数を16進数に変換するプログラムの作り方

    10進数の数値が入力として与えられたとき、その数値を16進数に変換するのが本記事の目的です。 コンピュータの世界では、16進数は基数16で表現され、10進数は基数10で表現されます。10進数は0〜9の値を使って表されるのに対し、16進数は0〜15の数字を持ちます。そのうち10は「A」、11は「B」、12は「C」、13は「D」、14は「E」、15は「F」として表されます。 10進数から16進数への変換手順 10進数を16進数に変換するには、以下の手順に従います。 まず、与えられた数値を変換先の基数で割ります。たとえば、6789を16進数に変換する場合、基数である16で6789を割り、商を求め

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

    コンピューターの内部では、すべてのデータが2進数(基数2)として扱われています。一方、私たちが日常的に使う10進数は「0〜9」の数字を組み合わせた基数10の記数法です。この記事では、C++を使って入力された10進数を2進数へ変換するプログラムの考え方と実装方法を解説します。10進数から2進数への変換手順10進数を2進数に変換する基本的な方法は、「2で割った余りを順番に記録していく」ものです。具体的には次の手順で行います。まず、変換したい数値を基数である2で割り、商と余りを求めます。余りが0であればその桁は「0」、1であれば「1」として記録します。続いて、得られた商をさらに2で割り、同じように余