循環少数を導き出す関数を作った

## 概要

ふと欲しくなったので、２つの整数を除算したときの解を、割り算の筆算のやり方で計算して、循環小数を割り出す関数を作ってみました。

## コード

```js
/**
 * @param {number} dividend 被除数。割られる数。「12÷3」で言うところの「12」
 * @param {number} divisor 除数。割る数。「12÷3」で言うところの「3」
 * @param {number=} maxFractionPartLength 小数部の最大桁数。この桁まで計算を行う。
 *     桁数が不足している場合、循環節を割り出せない場合がある。
 * @return {string} 計算結果の数値を示す文字列。
 *     計算を実行できない場合は "NaN"を返す。
 *     結果が実数の場合は"4.0"のような値を返す。
 *     循環小数の場合、循環節を"[]"で囲った"0.[3]"、"2.2[234]"のような値を返す。
 */
function division(dividend, divisor, maxFractionPartLength=100) {
  const quotientList = [];
  /*
   * @type {Array<number>} 小数部の各桁の剰余（余り）を保持する配列。循環節の検出に使用する。
   */
  const remainderList = [];
  /*
   * @type {number} 循環節が開始した小数部の位置を示す数値。
   */
  let recurringStartIndex = -1;
  /*
   * @type {boolean} 割り切れたり、循環節を検出できた等の理由で、除算が完了した場合はtrue。
   *     桁数の不足等で除算が完了していない場合はfalse。
   */
  let isComplete = false;

  /*
   * dividendとdivisorが有限の整数ではない場合、"NaN"を返す。
   */
  if (!(Number.isSafeInteger(dividend) && Number.isSafeInteger(divisor))) {
    return 'NaN';
  }

  /*
   * 小数部の桁数が maxFractionPartLength に到達するまでループで処理する。
   */
  for (let count=0; count < (maxFractionPartLength + 1); count++) {
    /*
     * 除算の剰余を求める。
     */
    const remainder = dividend % divisor;

    /*
     * 除算の商を求める。被除数から剰余を減算しておくことで、割り切れる除算を実行する。
     */
    const quotient = (dividend - remainder) / divisor;

    /*
     * 商を結果に追加。
     */
    quotientList.push(quotient);

    /*
     * 剰余が0の場合は、割り切れたと判定し、ループを抜ける。
     */
    if (remainder === 0) {
      isComplete = true;
      break;
    }

    /*
     * 剰余が、今まで計算した剰余のどれかと等しい場合、
     * 循環節を検出したと判定し、ループを抜ける。
     */
    recurringStartIndex = remainderList.indexOf(remainder);
    if (0 <= recurringStartIndex) {
      isComplete = true;
      break;
    }

    /*
     * 剰余を10倍し、次の除算の被除数と置き換える。
     */
    dividend = remainder * 10;

    /*
     * 剰余を、循環節を検出するための配列に加える。
     */
    remainderList.push(remainder);
  }

  /*
   * 整数部を抜き出す。
   */
  const realPart = quotientList[0];

  /*
   * 小数部の中で、循環節ではない部分の数値の配列を切り出す。
   */
  const nonRecurringPartList = quotientList.slice(
    1,
    (0 <= recurringStartIndex) ? (1 + recurringStartIndex) : undefined
  );

  /*
   * 小数部の中で、循環節の部分の数値の配列を切り出す。
   */
  const recurringPartList = (
    0 <= recurringStartIndex ?
      quotientList.slice(1 + recurringStartIndex) :
      []
  );

  /*
   * 小数部の文字列を生成する。
   */
  const fractionPart = (
    1 < quotientList.length ?
    (
      nonRecurringPartList.join('') +
      (
        0 < recurringPartList.length ?
          `[${recurringPartList.join('')}]` :
          ''
      )
    ) :
    '0'
  );

  return `${realPart}.${fractionPart}${isComplete ? '' : '…'}`;
}
```

## 使い方

```js
division(1, 4)
// "0.25"

division(12, 3)
// "4.0"
// // 整数の範囲で割り切れる場合も、"0"の小数部が付く。

division(1, 3)
// "0.[3]"
// // 循環節はこのように表記される。

division(1234, 555)
// "2.2[234]"

division(270, 269)
// "1.0037174721189591078066914498141263940520446096654275092936802973977695167286245353159851301115241635…"
// // 割り切れなかったり、循環節を見つけられなかったら、末尾に「…」が挿入される。

division(270, 269, 10)
// "1.0037174721…"
// // 最大桁数は第三引数に指定する。この場合は10

division(270, 269, 1000)
// "1.[0037174721189591078066914498141263940520446096654275092936802973977695167286245353159851301115241635687732342007434944237918215613382899628252788104089219330855018587360594795539033457249070631970260223048327137546468401486988847583643122676579925650557620817843866171]"
// // 十分に大きな最大桁数（この場合は1000）を指定することで、長い循環節を計算できる

division('A', 'B')
// "NaN"
// // 数値ではない値を指定されると"NaN"を返す

division(1, 0.5)
// "NaN"
// // 少数の除算には対応していない。そのうち対応するかも。

division(1, -3)
// "0.[-3]"
// // 負数が紛れ込む計算は、符号がおかしくなる場合がある。要改善
```