概要
ふと欲しくなったので、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]"
// // 負数が紛れ込む計算は、符号がおかしくなる場合がある。要改善