TypeScript PR

【ABC165B】TypeScriptでAtCoder「1%」問題に挑戦!BigIntの罠と正しい使い方

記事内に商品プロモーションを含む場合があります

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を使うには?

  1. tsconfig.jsontarget: "ES2020" 以上を指定:
{
"compilerOptions": {
"target": "ES2020"
}
}
  1. n を数値の末尾につけて BigInt リテラルにする(例:100n1000n
  2. parseInt ではなく BigInt() を使って入力値を変換

まとめ:AtCoderでTypeScriptを使う際の注意点

ポイント解説
BigInt必須精度が必要な整数計算は BigInt を使う
小数は使わない* 1.01 のような浮動小数演算はNG
ES2020以上に設定BigIntを使うには必須
型エラーに注意numberbigintは混在不可(明示的に分ける)

おまけ:コード全体

悩むのも勉強、まずは自分で考えて分からなかったご参考に〜。
※ざっくり書いてて、細部には拘ってないのでご容赦を・・・

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ユーザーの助けになれば嬉しいです!