2025年5月最近、競技プログラミングを始めました!
Typescriptの練習をしたいのでPython/C++ではなく、Typescript!
AtCoder Beginner Contest 165 の B 問題「1%」は、見た目はシンプルな複利計算の問題。しかし、JavaScript/TypeScript で解こうとしたとき、意外な落とし穴にハマりました。今回はその原因と解決方法を、初心者にも分かりやすく解説します。
問題の概要
高橋くんは100円を持っていて、毎年1%の利子(複利)が付きます。ある金額 X に達するには何年かかるかを求めよ。
条件:1 ≤ X ≤ 10^18(超でかい!)
つまり、毎年 floor(current * 1.01)
して、いつ current >= X
になるかを求めるだけ……のはずが?
最初の実装とその落とし穴
let current = 100;
let years = 0;
while (current < X) {
current = Math.floor(current * 1.01);
years++;
}
これ、一見うまく動くように見えるんです。が、AtCoderでは WA(不正解1つ)。なぜか?
※99-after-contest-01.txt
※ちなみに以下もNG(1.01で誤差出るかなと・・・試しました)
let currentFee = 100;
let count = 0;
while(numX > currentFee) {
currentFee += Math.floor(currentFee / 100);
count++;
}
浮動小数点誤差に注意!
TypeScript(= JavaScript)は number
型に 64bit の浮動小数点(IEEE 754)を使っています。これはだいたい 15桁の精度しか保証されません。
今回のXは 最大で10^18。つまり、15桁を超える値が出てくると 誤差 が発生し、1年分ズレることに。
例:
let a = 999999999999999999;
let b = a;
a += Math.floor(a / 100); // 正確:1009999999999999998
b *= 1.01; // 誤差で:1010000000000000000(ズレ)
解決策:BigIntを使う!
JavaScriptにはBigInt型があり、TypeScriptでも使えます。これは任意精度整数なので、10^18でも誤差なし!
let current = 100n;
let years = 0;
const target = BigInt(input);
while (current < target) {
current += current / 100n; // 1%利子を整数で追加
years++;
}
console.log(years);
TypeScriptでBigIntを使うには?
tsconfig.json
でtarget: "ES2020"
以上を指定:
{
"compilerOptions": {
"target": "ES2020"
}
}
n
を数値の末尾につけてBigInt
リテラルにする(例:100n
、1000n
)parseInt
ではなくBigInt()
を使って入力値を変換
まとめ:AtCoderでTypeScriptを使う際の注意点
ポイント | 解説 |
---|---|
BigInt必須 | 精度が必要な整数計算は BigInt を使う |
小数は使わない | * 1.01 のような浮動小数演算はNG |
ES2020 以上に設定 | BigInt を使うには必須 |
型エラーに注意 | number とbigint は混在不可(明示的に分ける) |
おまけ:コード全体
悩むのも勉強、まずは自分で考えて分からなかったご参考に〜。
※ざっくり書いてて、細部には拘ってないのでご容赦を・・・
function Main(input: string) {
const numX = BigInt(input.trim());
let currentFee = 100n;
let count = 0;
while(numX > currentFee) {
currentFee += currentFee / 100n;
count++;
}
console.log(count);
}
//*この行以降は編集しないでください(標準入出力から一度に読み込み、Mainを呼び出します)
Main(require("fs").readFileSync("/dev/stdin", "utf8"));
最後に
「小数演算なら楽勝!」と思っていた私も、まさかのWAに泣かされました。AtCoderでは「意図しない誤差」は致命傷。BigIntの使い方を学べたのは収穫でした。
この記事が、同じようにAtCoderに挑戦するTypeScriptユーザーの助けになれば嬉しいです!