/* 스프레드시트 엔진 A 무게 시험 — 큰 시트 불러오기 · 칸 하나 고칠 때 재계산 · 나눗셈 사슬 분모. * 수치를 JSON 으로 냄 → `test_spreadsheet_bench.py` 가 한도 안인지 봄. 적재 · 가짜 함수는 엔진 시험 헬퍼 것. */ const { A, C, E, F, G } = require("./helper_spreadsheet_engine.cjs"); const ms = (fn) => { const t = process.hrtime.bigint(); const out = fn(); return [Number(process.hrtime.bigint() - t) / 1e6, out]; }; const round = (x) => Math.round(x * 100) / 100; /** 칸 표 → 시트 하나 통합문서 JSON 글(불러오기 = JSON.parse + 엔진 만들기) */ function bookText(cells) { const 칸 = {}; for (const [k, v] of Object.entries(cells)) 칸[k] = typeof v === "string" ? { 식: v } : { 값: v }; return JSON.stringify({ 종류: "통합문서", 판: 1, 열: "b", 서식: [{}], 시트: [{ id: "s1", 이름: "무게", 칸 }] }); } /** 불러오기 · 칸 하나 고치기(가운데 · 맨 앞) 시간 */ function scenario(cells, edits) { const text = bookText(cells); const [parseMs, book] = ms(() => JSON.parse(text)); const [loadMs, engine] = ms(() => G.createCalcEngine(book)); const out = { cells: Object.keys(cells).length, kb: Math.round(text.length / 1024), parseMs: round(parseMs), loadMs: round(loadMs) }; for (const [label, a1, value] of edits) { const times = []; let redone = 0; for (let i = 0; i < 5; i++) { const fx = C.applyCommand(book, { 종류: "칸", 시트: "s1", 칸: { [a1]: { 값: value + i } } }); const [t, list] = ms(() => engine.update(fx.cells)); times.push(t); redone = list.length; } times.sort((x, y) => x - y); out[label] = { ms: round(times[2]), redone }; } return [out, engine]; } const N = 5000; const result = {}; // ① 얕은 시트 — 칸 1만(값 5천 · 식 5천) · 식마다 이웃 두 칸 · 끝에 합계 하나 { const cells = {}; for (let i = 1; i <= N; i++) cells[`A${i}`] = (i % 97) / 10; for (let i = 1; i < N; i++) cells[`B${i}`] = `ROUNDDOWN((A${i}+A${i + 1}/2)*1.3,2)`; cells[`B${N}`] = `SUM(B1:B${N - 1})`; const [out, engine] = scenario(cells, [ ["editMiddle", "A2500", 3], ["editFirst", "A1", 3], ]); out.total = E.toCalcValue(engine.value("s1", N - 1, 1)); result.shallow = out; } // ② 깊은 사슬 — 칸 1만 · 식 5천이 한 줄로 이어짐(맨 앞 고치면 5천 칸 모두 다시) { const cells = {}; for (let i = 1; i <= N; i++) cells[`A${i}`] = 1; cells.B1 = "A1"; for (let i = 2; i <= N; i++) cells[`B${i}`] = `B${i - 1}+A${i}*1.1`; const [out, engine] = scenario(cells, [ ["editLast", `A${N}`, 2], ["editFirst", "A1", 2], ]); out.end = E.toCalcValue(engine.value("s1", N - 1, 1)); result.chain = out; } // ③ 큰 시트 불러오기 — 실무 최대 모양(271 행 × 253 열) 에 가까운 6만 8천 칸 · 절반이 식 { const cells = {}; for (let r = 1; r <= 271; r++) for (let c = 0; c < 253; c++) { const a1 = A.toA1(r - 1, c); cells[a1] = c % 2 === 0 ? r + c / 100 : `${A.toA1(r - 1, c - 1)}*2+${A.toA1(Math.max(0, r - 2), c - 1)}/3`; } const [out] = scenario(cells, [["editOne", "A1", 5]]); result.bigSheet = out; } // ④ 나눗셈 사슬 — 분모가 커지는 식(1/소수 더하기 · 소수로 나누기) · 상한(10^40) 넘으면 30 자리 십진으로 굳힘 { const primes = []; for (let n = 2; primes.length < 2000; n++) if (primes.every((p) => n % p)) primes.push(n); const cells = { A1: 1, C1: 1 }; for (let i = 2; i <= 2000; i++) { cells[`A${i}`] = `A${i - 1}+1/${primes[i - 1]}`; // 역수 합 — 분모가 소수 곱으로 부풂 cells[`C${i}`] = `C${i - 1}/${primes[(i * 7) % 2000]}*${primes[(i * 13) % 2000]}`; // 곱 · 나눗셈 사슬 } const book = JSON.parse(bookText(cells)); const [loadMs, engine] = ms(() => G.createCalcEngine(book)); let maxDigits = 0; for (let i = 1; i <= 2000; i++) for (const c of [0, 2]) { const v = engine.value("s1", i - 1, c); maxDigits = Math.max(maxDigits, v.d.toString().length); } let dbl = 1; for (let i = 2; i <= 2000; i++) dbl += 1 / primes[i - 1]; const exact = Number(F.fracToString(engine.value("s1", 1999, 0))); result.divChain = { cells: 4000, loadMs: round(loadMs), maxDenominatorDigits: maxDigits, sum: F.fracToString(engine.value("s1", 1999, 0)), relErrVsDouble: Math.abs(exact - dbl) / dbl, }; } process.stdout.write(JSON.stringify(result));