LoginSignup
1
1

More than 5 years have passed since last update.

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

Posted at

概要

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

コード

/**
 * @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 ? '' : ''}`;
}

使い方

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]"
// // 負数が紛れ込む計算は、符号がおかしくなる場合がある。要改善
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1