From 46f927c575df39e1e9ed21e7016bb2a299e66eac Mon Sep 17 00:00:00 2001
From: hexjasur <3.15015959e+08+hexjasur@users.noreply.github.com>
Date: Mon, 19 Oct 2026 14:55:04 +0000
Subject: [PATCH 1/3] feat: add step-by-step Newton interpolation
---
.../methods/newton-interpolation/page.tsx | 68 +++
components/app-shell.tsx | 22 +-
components/dashboard/methods-hub.tsx | 23 +-
components/newton-interpolation-method.tsx | 475 ++++++++++++++++++
components/seo/json-ld.tsx | 1 +
lib/config.ts | 6 +
lib/i18n/locales/en.json | 75 ++-
lib/i18n/locales/ru.json | 75 ++-
lib/i18n/locales/uz.json | 75 ++-
lib/newton-interpolation.ts | 247 +++++++++
public/images/og/newton-interpolation.png | Bin 0 -> 108039 bytes
scripts/generate-og-images.ts | 12 +
12 files changed, 1056 insertions(+), 23 deletions(-)
create mode 100644 app/(workspace)/methods/newton-interpolation/page.tsx
create mode 100644 components/newton-interpolation-method.tsx
create mode 100644 lib/newton-interpolation.ts
create mode 100644 public/images/og/newton-interpolation.png
diff --git a/app/(workspace)/methods/newton-interpolation/page.tsx b/app/(workspace)/methods/newton-interpolation/page.tsx
new file mode 100644
index 0000000..477e221
--- /dev/null
+++ b/app/(workspace)/methods/newton-interpolation/page.tsx
@@ -0,0 +1,68 @@
+import type { Metadata } from 'next';
+import { NewtonInterpolationMethod } from '@/components/newton-interpolation-method';
+import { MethodJsonLd } from '@/components/seo/json-ld';
+import { siteConfig } from '@/lib/config';
+
+const route = '/methods/newton-interpolation';
+const pageTitle = 'Nyuton interpolyatsiyasi — bo‘lingan ayirmalar | Newton';
+const pageDescription =
+ 'Nyuton ko‘phadini hisoblang: bo‘lingan yoki chekli ayirmalar va P(x*) qiymati. Har bir formula hamda sonli qo‘yish qadam-baqadam ko‘rsatiladi.';
+const image = '/images/og/newton-interpolation.png';
+
+export const metadata: Metadata = {
+ title: pageTitle,
+ description: pageDescription,
+ keywords: [
+ 'Nyuton interpolyatsiyasi',
+ 'bo‘lingan ayirmalar',
+ 'Nyuton interpolyatsion ko‘phadi',
+ 'chekli ayirmalar usuli',
+ 'интерполяция Ньютона',
+ 'разделённые разности онлайн',
+ 'Newton interpolation calculator',
+ 'divided differences polynomial',
+ ],
+ alternates: { canonical: route },
+ openGraph: {
+ type: 'article',
+ title: 'Newton Interpolation Calculator | Nyuton interpolyatsiyasi',
+ description: pageDescription,
+ url: `${siteConfig.url}${route}`,
+ siteName: siteConfig.name,
+ images: [{ url: image, width: 1200, height: 630, alt: 'Newton divided differences interpolation calculator' }],
+ },
+ twitter: {
+ card: 'summary_large_image',
+ title: 'Newton Interpolation Calculator | Nyuton interpolyatsiyasi',
+ description: pageDescription,
+ images: [image],
+ },
+};
+
+const breadcrumbSchema = {
+ '@context': 'https://schema.org',
+ '@type': 'BreadcrumbList',
+ itemListElement: [
+ { '@type': 'ListItem', position: 1, name: siteConfig.name, item: siteConfig.url },
+ { '@type': 'ListItem', position: 2, name: 'Nyuton interpolyatsiyasi', item: `${siteConfig.url}${route}` },
+ ],
+};
+
+export default function NewtonInterpolationPage() {
+ return (
+ <>
+
+
+
+ >
+ );
+}
diff --git a/components/app-shell.tsx b/components/app-shell.tsx
index 16e8eae..4535b27 100644
--- a/components/app-shell.tsx
+++ b/components/app-shell.tsx
@@ -86,34 +86,40 @@ export function AppShell({
code: '08',
icon: Spline,
},
+ {
+ href: '/methods/newton-interpolation',
+ label: t('nav.newtonInterpolation'),
+ code: '09',
+ icon: Spline,
+ },
{
href: '/methods/rectangles',
label: t('nav.rectangles'),
- code: '09',
+ code: '10',
icon: Sigma,
},
{
href: '/methods/trapezoid',
label: t('nav.trapezoid'),
- code: '10',
+ code: '11',
icon: Sigma,
},
{
href: '/methods/simpson',
label: t('nav.simpson'),
- code: '11',
+ code: '12',
icon: Sigma,
},
{
href: '/methods/euler',
label: t('nav.euler'),
- code: '12',
+ code: '13',
icon: Activity,
},
{
href: '/methods/runge-kutta',
label: t('nav.rungeKutta'),
- code: '13',
+ code: '14',
icon: Activity,
},
],
@@ -145,6 +151,8 @@ export function AppShell({
? t('nav.learn')
: pathname.startsWith('/methods/bisection')
? t('nav.bisection')
+ : pathname.startsWith('/methods/newton-interpolation')
+ ? t('nav.newtonInterpolation')
: pathname.startsWith('/methods/newton')
? t('nav.newton')
: pathname.startsWith('/methods/sweep')
@@ -215,7 +223,9 @@ export function AppShell({
const active =
item.href === '/'
? pathname === '/'
- : pathname.startsWith(item.href);
+ : item.href === '/methods/newton'
+ ? pathname === item.href
+ : pathname === item.href || pathname.startsWith(`${item.href}/`);
return (
;
+}
+
+function texNumber(value: number): string {
+ const text = formatNewtonValue(value, 8);
+ const match = /^(-?\d+(?:\.\d+)?)e([+-]?\d+)$/i.exec(text);
+ if (match) return `${match[1]}\\times 10^{${Number(match[2])}}`;
+ return text;
+}
+
+function dividedDifferenceLabel(start: number, end: number): string {
+ return start === end
+ ? `f[x_{${start}}]`
+ : `f[x_{${start}},\\ldots,x_{${end}}]`;
+}
+
+function newtonBasisPolynomialTex(
+ result: NewtonInterpolationResult,
+ termCount: number,
+): string {
+ const terms: string[] = [];
+ for (let order = 0; order < termCount; order += 1) {
+ const coefficient = result.coefficients[order];
+ const magnitude = texNumber(Math.abs(coefficient));
+ const factors = result.basisNodes[order]
+ .map((node) => `(x - (${texNumber(node)}))`)
+ .join('\\,');
+ const term = order === 0
+ ? magnitude
+ : `${magnitude}${factors ? `\\,${factors}` : ''}`;
+ if (terms.length === 0) {
+ terms.push(coefficient < 0 ? `-${term}` : term);
+ } else {
+ terms.push(`${coefficient < 0 ? '-' : '+'} ${term}`);
+ }
+ }
+ return terms.join(' ') || '0';
+}
+
+function expandedPolynomialTex(coefficients: number[]): string {
+ const terms: string[] = [];
+ for (let power = coefficients.length - 1; power >= 0; power -= 1) {
+ const coefficient = coefficients[power];
+ if (coefficient === 0) continue;
+ const absolute = Math.abs(coefficient);
+ const coefficientText = absolute === 1 && power > 0 ? '' : texNumber(absolute);
+ const variable = power === 0
+ ? ''
+ : power === 1
+ ? 'x'
+ : `x^{${power}}`;
+ const term = `${coefficientText}${variable}`;
+ if (terms.length === 0) {
+ terms.push(coefficient < 0 ? `-${term}` : term);
+ } else {
+ terms.push(`${coefficient < 0 ? '-' : '+'} ${term}`);
+ }
+ }
+ return terms.join(' ') || '0';
+}
+
+function PointInputTable({
+ points,
+ onChange,
+}: {
+ points: NewtonInputPoint[];
+ onChange: (index: number, axis: 'x' | 'y', value: string) => void;
+}) {
+ const { t } = useI18n();
+ return (
+
+ );
+}
+
+function StepBlock({
+ number,
+ title,
+ children,
+}: {
+ number: string;
+ title: string;
+ children: ReactNode;
+}) {
+ return (
+
+
+ {number} ·
+ {title}
+
+ {children}
+
+ );
+}
+
+export function NewtonInterpolationMethod() {
+ const { t } = useI18n();
+ const [points, setPoints] = useState(DEFAULT_POINTS.map((point) => ({ ...point })));
+ const [targetX, setTargetX] = useState(DEFAULT_TARGET);
+ const [mode, setMode] = useState('divided');
+ const [result, setResult] = useState(() =>
+ calculateNewtonInterpolation(DEFAULT_POINTS, DEFAULT_TARGET, 'divided'),
+ );
+ const [error, setError] = useState('');
+
+ function calculate(inputPoints = points, inputTarget = targetX, inputMode = mode) {
+ setError('');
+ try {
+ setResult(calculateNewtonInterpolation(inputPoints, inputTarget, inputMode));
+ } catch (cause) {
+ setResult(null);
+ setError(
+ cause instanceof Error
+ ? t(cause.message)
+ : t('newtonInterpolation.err_general'),
+ );
+ }
+ }
+
+ function updatePoint(index: number, axis: 'x' | 'y', value: string) {
+ setPoints((current) =>
+ current.map((point, pointIndex) =>
+ pointIndex === index ? { ...point, [axis]: value } : point,
+ ),
+ );
+ setResult(null);
+ setError('');
+ }
+
+ function applyExample(index: number) {
+ const example = EXAMPLES[index];
+ const nextPoints = example.points.map((point) => ({ ...point }));
+ setPoints(nextPoints);
+ setTargetX(example.targetX);
+ setMode(example.mode);
+ calculate(nextPoints, example.targetX, example.mode);
+ }
+
+ function addPoint() {
+ if (points.length >= MAX_POINTS) return;
+ setPoints((current) => [...current, { x: '', y: '' }]);
+ setResult(null);
+ setError('');
+ }
+
+ function removePoint() {
+ if (points.length <= 2) return;
+ setPoints((current) => current.slice(0, -1));
+ setResult(null);
+ setError('');
+ }
+
+ const anchorIndex = result?.mode === 'finite' && result.direction === 'backward'
+ ? (result.points.length - 1)
+ : 0;
+
+ return (
+
+
+
}
+ conditionSub={t('newtonInterpolation.hero_condition')}
+ />
+
},
+ { n: '03', t: t('newtonInterpolation.map_3_title'), s:
},
+ { n: '04', t: t('newtonInterpolation.map_4_title'), s: t('newtonInterpolation.map_4_desc') },
+ ]}
+ />
+
+
+
+
+ }>
+ {t('newtonInterpolation.note_points')} 1/2, -1. {t('newtonInterpolation.note_unique')}
+
+
+
+
+
+ {result ? (
+ <>
+
({
+ order: step.order,
+ i: step.index,
+ left_value: formatNewtonValue(step.leftValue),
+ right_value: formatNewtonValue(step.rightValue),
+ denominator: step.denominator === null ? '' : formatNewtonValue(step.denominator),
+ result: formatNewtonValue(step.result),
+ }))}
+ csvFileName="newton-interpolation-steps.csv"
+ imageFileName="newton-interpolation-steps.png"
+ />
+
+
+
+
+ {t('newtonInterpolation.step_points_desc', { count: result.points.length })}
+
+
+ | i | xᵢ | yᵢ |
+ {result.points.map((point) => | {point.index} | {formatNewtonValue(point.x)} | {formatNewtonValue(point.y)} |
)}
+
+
+ {t('newtonInterpolation.step_points_check')}
+
+
+
+ {result.mode === 'divided' ? t('newtonInterpolation.divided_intro') : t('newtonInterpolation.finite_intro', { direction: t(`newtonInterpolation.${result.direction}`), h: formatNewtonValue(result.spacing ?? 0) })}
+
+ {result.mode === 'divided'
+ ?
+ : result.direction === 'backward'
+ ?
+ : }
+
+
+ {result.differenceSteps.map((step) => {
+ const isBackward = result.mode === 'finite' && result.direction === 'backward';
+ const formulaTex = result.mode === 'divided'
+ ? `${dividedDifferenceLabel(step.index, step.index + step.order)}=\\frac{${dividedDifferenceLabel(step.index + 1, step.index + step.order)}-${dividedDifferenceLabel(step.index, step.index + step.order - 1)}}{x_{${step.index + step.order}}-x_{${step.index}}}=\\frac{${texNumber(step.leftValue)}-(${texNumber(step.rightValue)})}{${texNumber(step.endX)}-(${texNumber(step.startX)})}=${texNumber(step.result)}`
+ : isBackward
+ ? `\\nabla^{${step.order}}y_{${step.index}}=${step.order === 1 ? `y_{${step.index}}-y_{${step.index - 1}}` : `\\nabla^{${step.order - 1}}y_{${step.index}}-\\nabla^{${step.order - 1}}y_{${step.index - 1}}`}=${texNumber(step.leftValue)}-(${texNumber(step.rightValue)})=${texNumber(step.result)}`
+ : `${step.order === 1 ? '\\Delta y' : `\\Delta^{${step.order}}y`}_{${step.index}}=${step.order === 1 ? `y_{${step.index + 1}}-y_{${step.index}}` : `\\Delta^{${step.order - 1}}y_{${step.index + 1}}-\\Delta^{${step.order - 1}}y_{${step.index}}`}=${texNumber(step.leftValue)}-(${texNumber(step.rightValue)})=${texNumber(step.result)}`;
+ return (
+
+
{t('newtonInterpolation.difference_cell', { order: step.order, index: step.index })}
+
+
+ );
+ })}
+
+
+ {t('newtonInterpolation.table_summary')}
+
+
+ | i | xᵢ | yᵢ | {result.points.slice(1).map((_, index) => {result.mode === 'divided' ? `f[·] ${index + 1}` : `${result.direction === 'backward' ? '∇' : 'Δ'}${index + 1}y`} | )}
+ {result.points.map((point, row) => | {point.index} | {formatNewtonValue(point.x)} | {formatNewtonValue(point.y)} | {result.points.slice(1).map((_, orderIndex) => { const order = orderIndex + 1; const validCell = result.mode === 'finite' && result.direction === 'backward' ? row >= order : row < result.points.length - order; return {validCell ? formatNewtonValue(result.differenceTable[row][order]) : '—'} | ; })}
)}
+
+
+
+
+
+
+ {t('newtonInterpolation.polynomial_intro')}
+
+
+ {result.coefficients.map((coefficient, order) => {
+ let coefficientTex: string;
+ if (order === 0) {
+ coefficientTex = `a_0=y_{${anchorIndex}}=${texNumber(coefficient)}`;
+ } else if (result.mode === 'divided') {
+ coefficientTex = `a_${order}=${dividedDifferenceLabel(0, order)}=${texNumber(coefficient)}`;
+ } else {
+ const symbol = result.direction === 'forward' ? '\\Delta' : '\\nabla';
+ const nodeIndex = result.direction === 'forward' ? 0 : result.points.length - 1;
+ const differenceRow = result.direction === 'forward' ? 0 : result.points.length - 1;
+ coefficientTex = `a_${order}=\\frac{${symbol}^{${order}}y_{${nodeIndex}}}{${order}!\\,h^{${order}}}=\\frac{${texNumber(result.differenceTable[differenceRow][order])}}{${order}!\\,(${texNumber(result.spacing ?? 0)})^{${order}}}=${texNumber(coefficient)}`;
+ }
+ return (
+
+ );
+ })}
+
+
+
{t('newtonInterpolation.expanded_polynomial')}
+
+
+
+
+
+ {t('newtonInterpolation.evaluate_intro', { x: formatNewtonValue(result.targetX) })}
+
+ {result.evaluationTerms.map((term) => {
+ const factors = term.nodes.map((node) => `(${texNumber(result.targetX)}-(${texNumber(node)}))`).join('\\cdot ') || '1';
+ const termFormula = `T_${term.order}(${texNumber(result.targetX)})=${texNumber(term.coefficient)}\\cdot ${factors}=${texNumber(term.value)}`;
+ const previous = term.order === 0 ? '0' : texNumber(result.evaluationTerms[term.order - 1].cumulative);
+ return (
+
+ );
+ })}
+
+
+
{t('newtonInterpolation.final_value')}
+
+
+
+
+
+ >
+ ) : (
+
+
+ {error || t('newtonInterpolation.waiting')}
+
+ )}
+
+
+
+
+
+
+
{t('newtonInterpolation.theory_divided_title')}
+
{t('newtonInterpolation.theory_divided_desc')}
+
+
+
{t('newtonInterpolation.theory_finite_title')}
+
{t('newtonInterpolation.theory_finite_desc')}
+
+
+
+
+ );
+}
diff --git a/components/seo/json-ld.tsx b/components/seo/json-ld.tsx
index ed4f9b1..e9bfc86 100644
--- a/components/seo/json-ld.tsx
+++ b/components/seo/json-ld.tsx
@@ -45,6 +45,7 @@ export function RootJsonLd() {
'Eyler usuli (Oddiy differensial tenglamalarni yechish)',
'Runge-Kutta 4-tartibli usuli (Yuqori aniqlikdagi ODU yechish)',
'Lagranj interpolyatsiya koʻphadi',
+ 'Nyuton interpolyatsiya ko‘phadi (bo‘lingan ayirmalar)',
'Simpson parabolalar formulasi bilan integrallash',
'Trapetsiyalar usuli bilan integrallash',
'Toʻgʻri toʻrtburchaklar usuli bilan integrallash',
diff --git a/lib/config.ts b/lib/config.ts
index 74b2528..446fe5b 100644
--- a/lib/config.ts
+++ b/lib/config.ts
@@ -34,6 +34,8 @@ export const siteConfig = {
'trapetsiya usuli',
'togri tortburchaklar usuli',
'lagranj interpolyatsiyasi',
+ 'nyuton interpolyatsiyasi',
+ 'nyutonning bo‘lingan ayirmalari',
'progonka usuli',
'quvib otish usuli',
'tenglama ildizini topish',
@@ -55,6 +57,8 @@ export const siteConfig = {
'метод трапеций',
'метод прямоугольников',
'интерполяция лагранжа',
+ 'интерполяция ньютона',
+ 'разделённые разности ньютона',
'метод прогонки',
'решение дифференциальных уравнений онлайн',
'численное интегрирование',
@@ -71,6 +75,8 @@ export const siteConfig = {
'trapezoidal rule',
'rectangles rule',
'lagrange interpolation',
+ 'newton interpolation',
+ 'newton divided differences',
'thomas algorithm',
'tridiagonal matrix algorithm',
'math solver online',
diff --git a/lib/i18n/locales/en.json b/lib/i18n/locales/en.json
index b7fbba9..b8b89a5 100644
--- a/lib/i18n/locales/en.json
+++ b/lib/i18n/locales/en.json
@@ -16,7 +16,8 @@
"v2_tooltip": "Open legacy v2.sonli-usullar.uz version",
"sweep": "Thomas Method",
"rungeKutta": "Runge–Kutta 4th Order",
- "learn": "Uzbek learning guides"
+ "learn": "Uzbek learning guides",
+ "newtonInterpolation": "Newton Interpolation"
},
"about": {
"strip": "DOC-002 / ABOUT",
@@ -77,7 +78,7 @@
"hero_title_2": ", step by step.",
"hero_lead": "Learn numerical methods through real interactive computation, not just theory.",
"stat1_label": "Active methods",
- "stat1_val": "09 Methods",
+ "stat1_val": "10 Methods",
"stat2_label": "Domains",
"stat2_val": "5 Categories",
"stat3_label": "Compute environment",
@@ -85,7 +86,7 @@
"overview": "Workspace status",
"catalog_eyebrow": "02 / METHODS HUB",
"catalog_title": "Interactive Numerical Methods Catalog",
- "catalog_desc": "Explore all 9 core numerical computation algorithms, their mathematical formulas, and live interactive labs.",
+ "catalog_desc": "Explore all 10 core numerical computation algorithms, their mathematical formulas, and live interactive labs.",
"cat_all": "All",
"cat_equations": "Equations",
"cat_interpolation": "Interpolation",
@@ -109,7 +110,8 @@
"learn_cta": "Read the guides",
"desc_sweep": "Solve tridiagonal linear systems with forward and backward sweeps.",
"cat_linear": "Linear systems",
- "desc_rungeKutta": "Solve a first-order initial value problem with the classical fourth-order Runge–Kutta method."
+ "desc_rungeKutta": "Solve a first-order initial value problem with the classical fourth-order Runge–Kutta method.",
+ "desc_newtonInterpolation": "Build the Newton polynomial from divided differences, compare finite differences, and evaluate P(x*) step by step."
},
"actions": {
"tools_badge": "EXPORT / TOOLS",
@@ -724,5 +726,70 @@
},
"a11y": {
"backToTop": "Back to top"
+ },
+ "newtonInterpolation": {
+ "strip": "Newton Interpolation",
+ "hero_eyebrow": "INTERPOLATION / NEWTON",
+ "hero_title": "Newton interpolation",
+ "hero_highlight": "polynomial",
+ "hero_lead": "Enter data nodes. Construct the polynomial with divided differences or equally spaced finite differences, then see exactly how the value at any x is obtained.",
+ "hero_condition": "Divided differences accept any distinct x nodes. Finite differences require equally spaced x nodes.",
+ "map_eyebrow": "CALCULATION PATH",
+ "map_title": "How is the answer built?",
+ "map_1_title": "Validate the nodes",
+ "map_1_desc": "Read each (xᵢ, yᵢ) point and make sure no x value is repeated.",
+ "map_2_title": "Calculate differences step by step",
+ "map_3_title": "Build the polynomial",
+ "map_4_title": "Evaluate at x*",
+ "map_4_desc": "Evaluate every factor and term separately, then add it to the running sum.",
+ "input_eyebrow": "INPUT / NODES",
+ "input_title": "Enter interpolation points",
+ "note_points": "Enter xᵢ and yᵢ as real numbers or fractions such as 1/2.",
+ "note_unique": "Every xᵢ value must be distinct.",
+ "point_x": "x value for point {index}",
+ "point_y": "y value for point {index}",
+ "point_controls": "Add a node or remove the last one",
+ "add_point": "Add point",
+ "remove_point": "Remove last point",
+ "mode": "Difference method",
+ "mode_divided": "Divided differences",
+ "mode_finite": "Finite differences",
+ "target_x": "Evaluate at x*",
+ "calculate": "Calculate step by step",
+ "example_1": "Example 1 · y=x², equal spacing (1, 2, 3), x*=1.5",
+ "example_2": "Example 2 · nonuniform nodes (divided), x*=2",
+ "example_3": "Example 3 · classic v2 nodes, x*=1/4",
+ "steps_eyebrow": "PRINT STREAM / CALCULATION",
+ "steps_title": "Step-by-step solution",
+ "step_points": "Input nodes",
+ "step_points_desc": "The calculation uses {count} nodes. Their x coordinates must be distinct.",
+ "step_points_check": "Why check this? Divided differences divide by xⱼ−xᵢ; repeated x values make the denominator zero.",
+ "step_differences": "Fill the difference table by hand",
+ "divided_intro": "Each higher-order difference uses two neighboring differences from the previous order. Divide their difference by the span between the corresponding x nodes.",
+ "finite_intro": "The nodes have equal spacing h={h}. Each new {direction} finite difference is the difference of two neighboring entries from the previous order.",
+ "forward": "forward",
+ "backward": "backward",
+ "difference_cell": "Order {order} · i={index}",
+ "table_summary": "View the complete difference table",
+ "step_polynomial": "Construct the Newton polynomial",
+ "polynomial_intro": "Each coefficient aᵢ comes from the difference table. Add one coefficient and its new product factor at each stage to build the polynomial.",
+ "expanded_polynomial": "Expanded polynomial",
+ "step_evaluate": "Evaluate at x*",
+ "evaluate_intro": "Substitute x*={x} into the polynomial basis and calculate each term in sequence.",
+ "final_value": "Final interpolated value",
+ "theory_eyebrow": "THEORY / GUIDE",
+ "theory_title": "Which difference method should I use?",
+ "theory_divided_title": "Divided differences",
+ "theory_divided_desc": "Nodes do not need equal spacing. Newton divided differences work for any distinct x values and make it straightforward to add another node.",
+ "theory_finite_title": "Finite differences",
+ "theory_finite_desc": "Use only with equally spaced nodes. The table gives finite differences; the forward or backward formula is selected based on whether x* is closer to the start or end.",
+ "waiting": "The initial example is shown. After changing nodes or the method, calculate again to get an updated solution.",
+ "err_points_min": "Enter at least 2 points.",
+ "err_points_max": "You can enter at most 12 points.",
+ "err_real": "x, y, and x* values must be finite real numbers or fractions such as 1/2.",
+ "err_duplicate": "The x coordinates must be distinct.",
+ "err_spacing": "Finite differences require equal spacing between all x nodes. Choose divided differences instead.",
+ "err_overflow": "The calculation exceeded the supported numeric range. Try smaller values or fewer nodes.",
+ "err_general": "Could not calculate. Check the entered points."
}
}
diff --git a/lib/i18n/locales/ru.json b/lib/i18n/locales/ru.json
index 084aeb5..8e3b8b3 100644
--- a/lib/i18n/locales/ru.json
+++ b/lib/i18n/locales/ru.json
@@ -16,7 +16,8 @@
"v2_tooltip": "Открыть старую версию v2.sonli-usullar.uz",
"sweep": "Метод прогонки",
"rungeKutta": "Метод Рунге — Кутты 4-го порядка",
- "learn": "Учебник на узбекском"
+ "learn": "Учебник на узбекском",
+ "newtonInterpolation": "Интерполяция Ньютона"
},
"about": {
"strip": "DOC-002 / О ПРОЕКТЕ",
@@ -77,7 +78,7 @@
"hero_title_2": ", понимая каждый шаг.",
"hero_lead": "Изучайте численные методы не только в теории, но и с помощью интерактивных вычислений.",
"stat1_label": "Активные методы",
- "stat1_val": "09 методов",
+ "stat1_val": "10 методов",
"stat2_label": "Области",
"stat2_val": "5 категорий",
"stat3_label": "Среда вычислений",
@@ -85,7 +86,7 @@
"overview": "Состояние среды",
"catalog_eyebrow": "02 / КАТАЛОГ МЕТОДОВ",
"catalog_title": "Каталог интерактивных численных методов",
- "catalog_desc": "Все 9 ключевых алгоритмов вычислений, их математические формулы и интерактивные лаборатории.",
+ "catalog_desc": "Все 10 ключевых алгоритмов вычислений, их математические формулы и интерактивные лаборатории.",
"cat_all": "Все",
"cat_equations": "Уравнения",
"cat_interpolation": "Интерполяция",
@@ -109,7 +110,8 @@
"learn_cta": "Читать учебник",
"desc_sweep": "Решение трехдиагональных систем прямым и обратным ходом.",
"cat_linear": "Линейные системы",
- "desc_rungeKutta": "Решение задачи Коши для ОДУ классическим методом Рунге — Кутты четвертого порядка."
+ "desc_rungeKutta": "Решение задачи Коши для ОДУ классическим методом Рунге — Кутты четвертого порядка.",
+ "desc_newtonInterpolation": "Постройте многочлен Ньютона по разделённым разностям, сравните конечные разности и пошагово вычислите P(x*)."
},
"actions": {
"tools_badge": "ЭКСПОРТ / ИНСТРУМЕНТЫ",
@@ -724,5 +726,70 @@
},
"a11y": {
"backToTop": "Вернуться в начало страницы"
+ },
+ "newtonInterpolation": {
+ "strip": "Интерполяция Ньютона",
+ "hero_eyebrow": "ИНТЕРПОЛЯЦИЯ / НЬЮТОН",
+ "hero_title": "Интерполяционный",
+ "hero_highlight": "многочлен Ньютона",
+ "hero_lead": "Введите узловые точки. Постройте многочлен по разделённым разностям или конечным разностям при равноотстоящих узлах и проследите, откуда берётся значение в любой точке x.",
+ "hero_condition": "Разделённые разности допускают любые различные узлы x. Для конечных разностей шаг между узлами должен быть одинаковым.",
+ "map_eyebrow": "ХОД РАСЧЁТА",
+ "map_title": "Как получается ответ?",
+ "map_1_title": "Проверить узлы",
+ "map_1_desc": "Считайте каждую точку (xᵢ, yᵢ) и убедитесь, что значения x не повторяются.",
+ "map_2_title": "Пошагово вычислить разности",
+ "map_3_title": "Построить многочлен",
+ "map_4_title": "Вычислить при x*",
+ "map_4_desc": "Отдельно вычислите каждый множитель и член, затем прибавьте его к текущей сумме.",
+ "input_eyebrow": "ВВОД / УЗЛЫ",
+ "input_title": "Введите точки интерполяции",
+ "note_points": "Введите xᵢ и yᵢ как действительные числа или дроби, например 1/2.",
+ "note_unique": "Все значения xᵢ должны различаться.",
+ "point_x": "Значение x точки {index}",
+ "point_y": "Значение y точки {index}",
+ "point_controls": "Добавить узел или удалить последний",
+ "add_point": "Добавить точку",
+ "remove_point": "Удалить последнюю точку",
+ "mode": "Метод разностей",
+ "mode_divided": "Разделённые разности",
+ "mode_finite": "Конечные разности",
+ "target_x": "Вычислить при x*",
+ "calculate": "Вычислить пошагово",
+ "example_1": "Пример 1 · y=x², равный шаг (1, 2, 3), x*=1.5",
+ "example_2": "Пример 2 · неравномерные узлы (разделённые), x*=2",
+ "example_3": "Пример 3 · классические узлы v2, x*=1/4",
+ "steps_eyebrow": "PRINT STREAM / РАСЧЁТ",
+ "steps_title": "Пошаговое решение",
+ "step_points": "Введённые узлы",
+ "step_points_desc": "Используются {count} узла. Их координаты x должны различаться.",
+ "step_points_check": "Зачем это нужно? В разделённых разностях делим на xⱼ−xᵢ; при совпадающих x знаменатель равен нулю.",
+ "step_differences": "Последовательно заполнить таблицу разностей",
+ "divided_intro": "Каждая разность следующего порядка строится по двум соседним разностям предыдущего порядка. Их разность делится на расстояние между соответствующими узлами x.",
+ "finite_intro": "Узлы равноотстоящие с шагом h={h}. Каждая новая {direction} конечная разность равна разности двух соседних элементов предыдущего порядка.",
+ "forward": "прямая",
+ "backward": "обратная",
+ "difference_cell": "Порядок {order} · i={index}",
+ "table_summary": "Показать полную таблицу разностей",
+ "step_polynomial": "Построить многочлен Ньютона",
+ "polynomial_intro": "Каждый коэффициент aᵢ берётся из таблицы разностей. На каждом этапе к предыдущему многочлену добавляются коэффициент и новый множитель.",
+ "expanded_polynomial": "Развёрнутый многочлен",
+ "step_evaluate": "Вычислить при x*",
+ "evaluate_intro": "Подставьте x*={x} в базис многочлена и вычислите каждый член по порядку.",
+ "final_value": "Итоговое значение интерполяции",
+ "theory_eyebrow": "ТЕОРИЯ / СПРАВКА",
+ "theory_title": "Какой метод разностей выбрать?",
+ "theory_divided_title": "Разделённые разности",
+ "theory_divided_desc": "Расстояния между узлами могут быть разными. Разделённые разности Ньютона подходят для любых различных x и позволяют удобно добавлять новые узлы.",
+ "theory_finite_title": "Конечные разности",
+ "theory_finite_desc": "Используйте только для равноотстоящих узлов. Таблица даёт прямые или обратные разности; формула выбирается автоматически по близости x* к началу или концу таблицы.",
+ "waiting": "Показан начальный пример. После изменения узлов или метода выполните расчёт повторно.",
+ "err_points_min": "Введите не менее 2 точек.",
+ "err_points_max": "Можно ввести не более 12 точек.",
+ "err_real": "x, y и x* должны быть конечными действительными числами или дробями, например 1/2.",
+ "err_duplicate": "Координаты x должны различаться.",
+ "err_spacing": "Для конечных разностей шаг между всеми узлами x должен быть одинаковым. Вместо этого выберите разделённые разности.",
+ "err_overflow": "Расчёт вышел за поддерживаемый числовой диапазон. Попробуйте меньшие значения или меньшее число узлов.",
+ "err_general": "Не удалось выполнить расчёт. Проверьте введённые точки."
}
}
diff --git a/lib/i18n/locales/uz.json b/lib/i18n/locales/uz.json
index 76c0a4a..4f77981 100644
--- a/lib/i18n/locales/uz.json
+++ b/lib/i18n/locales/uz.json
@@ -16,7 +16,8 @@
"v2_tooltip": "Eski v2.sonli-usullar.uz talqiniga o'tish",
"sweep": "Haydash usuli",
"rungeKutta": "Runge–Kutta 4-tartibli usuli",
- "learn": "O‘zbekcha qo‘llanma"
+ "learn": "O‘zbekcha qo‘llanma",
+ "newtonInterpolation": "Nyuton interpolyatsiyasi"
},
"about": {
"strip": "DOC-002 / HAQIDA",
@@ -77,7 +78,7 @@
"hero_title_2": ", har qadamni tushuning.",
"hero_lead": "Sonli usullarni nazariya bilan emas, real interaktiv hisoblash orqali o'rganing.",
"stat1_label": "Faol usullar",
- "stat1_val": "09 ta metod",
+ "stat1_val": "10 ta metod",
"stat2_label": "Hisoblash sohalari",
"stat2_val": "5 ta toifa",
"stat3_label": "Hisoblash muhiti",
@@ -85,7 +86,7 @@
"overview": "Workspace holati",
"catalog_eyebrow": "02 / METODLAR KATALOGI",
"catalog_title": "Interaktiv sonli usullar laboratoriyasi",
- "catalog_desc": "Barcha 9 ta asosiy hisoblash algoritmlari, ularning matematik formulalari va interaktiv parametrlari.",
+ "catalog_desc": "Barcha 10 ta asosiy hisoblash algoritmlari, ularning matematik formulalari va interaktiv parametrlari.",
"cat_all": "Barchasi",
"cat_equations": "Tenglamalar",
"cat_interpolation": "Interpolyatsiya",
@@ -109,7 +110,8 @@
"learn_cta": "Qo‘llanmani o‘qish",
"desc_sweep": "Uch diagonalli chiziqli tenglamalar sistemasini to‘g‘ri va teskari yurish bilan yechish.",
"cat_linear": "Chiziqli sistemalar",
- "desc_rungeKutta": "Birinchi tartibli Koshi masalasini klassik to‘rtinchi tartibli Runge–Kutta usuli bilan yeching."
+ "desc_rungeKutta": "Birinchi tartibli Koshi masalasini klassik to‘rtinchi tartibli Runge–Kutta usuli bilan yeching.",
+ "desc_newtonInterpolation": "Bo‘lingan ayirmalar orqali Nyuton ko‘phadini tuzing, chekli ayirmalarni taqqoslang va P(x*) qiymatini qadam-baqadam hisoblang."
},
"actions": {
"tools_badge": "EXPORT / TOOLS",
@@ -724,5 +726,70 @@
},
"a11y": {
"backToTop": "Sahifa boshiga qaytish"
+ },
+ "newtonInterpolation": {
+ "strip": "Nyuton interpolyatsiyasi",
+ "hero_eyebrow": "INTERPOLYATSIYA / NYUTON",
+ "hero_title": "Nyuton interpolyatsion",
+ "hero_highlight": "ko‘phadi",
+ "hero_lead": "Tugun nuqtalarni kiriting. Bo‘lingan ayirmalar yoki teng oraliqli chekli ayirmalar orqali ko‘phadni quring va istalgan x dagi qiymat qayerdan kelganini ko‘ring.",
+ "hero_condition": "TBA usuli takrorlanmaydigan ixtiyoriy x tugunlarida ishlaydi. TCHA usuli uchun x tugunlar orasidagi masofa teng bo‘lishi kerak.",
+ "map_eyebrow": "HISOBLASH YO‘LI",
+ "map_title": "Javob qanday hosil bo‘ladi?",
+ "map_1_title": "Tugunlarni tekshirish",
+ "map_1_desc": "Har bir (xᵢ, yᵢ) nuqtani o‘qing va takroriy x qiymat yo‘qligini tekshiring.",
+ "map_2_title": "Ayirmalarni bosqichma-bosqich topish",
+ "map_3_title": "Ko‘phadni qurish",
+ "map_4_title": "x* da hisoblash",
+ "map_4_desc": "Har bir ko‘paytuvchi va hadni alohida hisoblab, qisman yig‘indiga qo‘shing.",
+ "input_eyebrow": "KIRITISH / TUGUNLAR",
+ "input_title": "Nuqtalarni kiriting",
+ "note_points": "xᵢ va yᵢ qiymatiga haqiqiy son yoki 1/2 kabi kasr kiriting.",
+ "note_unique": "xᵢ qiymatlar takrorlanmasligi kerak.",
+ "point_x": "{index}-nuqtaning x qiymati",
+ "point_y": "{index}-nuqtaning y qiymati",
+ "point_controls": "Tugun qo‘shish yoki oxirgisini o‘chirish",
+ "add_point": "Nuqta qo‘shish",
+ "remove_point": "Oxirgi nuqtani o‘chirish",
+ "mode": "Ayirma usuli",
+ "mode_divided": "Bo‘lingan ayirmalar (TBA)",
+ "mode_finite": "Chekli ayirmalar (TCHA)",
+ "target_x": "Hisoblanadigan x*",
+ "calculate": "Qadam-baqadam hisoblash",
+ "example_1": "Misol 1 · y=x², teng qadamlar (1, 2, 3), x*=1.5",
+ "example_2": "Misol 2 · teng bo‘lmagan tugunlar (TBA), x*=2",
+ "example_3": "Misol 3 · v2 dagi klassik nuqtalar, x*=1/4",
+ "steps_eyebrow": "PRINT STREAM / HISOBLASH",
+ "steps_title": "Qadam-baqadam yechim",
+ "step_points": "Kiritilgan tugunlar",
+ "step_points_desc": "Avval {count} ta tugun olindi. Ularning x koordinatalari turlicha bo‘lishi shart.",
+ "step_points_check": "Nega tekshiramiz? Bo‘lingan ayirmada xⱼ−xᵢ maxrajga kiradi; x lar teng bo‘lsa, maxraj nol bo‘ladi.",
+ "step_differences": "Ayirmalar jadvalini qo‘lda to‘ldirish",
+ "divided_intro": "Har bir yangi daraja oldingi darajadagi ikkita qo‘shni ayirma bilan topiladi. Ularning farqini x koordinatalari oralig‘iga bo‘lamiz.",
+ "finite_intro": "Tugunlar teng h={h} masofada. {direction} chekli ayirmalar jadvalining har bir yangi katagi yuqoridagi ikkita katak farqidan topiladi.",
+ "forward": "oldinga",
+ "backward": "orqaga",
+ "difference_cell": "{order}-tartib · i={index}",
+ "table_summary": "Barcha hisoblangan ayirmalarni jadvalda ko‘rish",
+ "step_polynomial": "Nyuton ko‘phadini qurish",
+ "polynomial_intro": "aᵢ koeffitsiyenti ayirmalar jadvalidan olinadi. Keyingi had har safar oldingi ko‘phadga bitta koeffitsiyent va yangi ko‘paytuvchini qo‘shib hosil qilinadi.",
+ "expanded_polynomial": "Ko‘phadning yoyilgan ko‘rinishi",
+ "step_evaluate": "x* da qiymatni hisoblash",
+ "evaluate_intro": "Endi x*={x} qiymatini ko‘phad asoslariga qo‘yib, har bir hadni ketma-ket hisoblaymiz.",
+ "final_value": "Yakuniy interpolatsion qiymat",
+ "theory_eyebrow": "NAZARIYA / QO‘LLANMA",
+ "theory_title": "Qaysi ayirma usulini tanlayman?",
+ "theory_divided_title": "Bo‘lingan ayirmalar (TBA)",
+ "theory_divided_desc": "Tugunlar oralig‘i teng bo‘lishi shart emas. Nyutonning bo‘lingan ayirmalari turlicha x qiymatlarida ishlaydi; yangi tugun qo‘shish ham qulay.",
+ "theory_finite_title": "Chekli ayirmalar (TCHA)",
+ "theory_finite_desc": "Faqat teng qadamli tugunlarda ishlaydi. Jadval chekli ayirmalarni beradi; x* jadval boshiga yaqin bo‘lsa oldinga, oxiriga yaqin bo‘lsa orqaga formula tanlanadi.",
+ "waiting": "Dastlabki misol ko‘rsatilgan. Tugun yoki usulni o‘zgartirsangiz, yangi javobni qayta hisoblang.",
+ "err_points_min": "Kamida 2 ta tugun kerak.",
+ "err_points_max": "Ko‘pi bilan 12 ta tugun kiritish mumkin.",
+ "err_real": "x, y va x* qiymatlari chekli haqiqiy son yoki 1/2 kabi kasr bo‘lishi kerak.",
+ "err_duplicate": "x koordinatalari takrorlanmasligi kerak.",
+ "err_spacing": "Chekli ayirmalar usuli uchun barcha x tugunlar orasidagi qadam teng bo‘lishi shart. Bo‘lingan ayirmalarni tanlang.",
+ "err_overflow": "Hisoblashda sonlar diapazoni oshib ketdi. Kichikroq sonlar yoki kamroq tugun kiriting.",
+ "err_general": "Hisoblab bo‘lmadi. Kiritilgan nuqtalarni tekshiring."
}
}
diff --git a/lib/newton-interpolation.ts b/lib/newton-interpolation.ts
new file mode 100644
index 0000000..5ef4679
--- /dev/null
+++ b/lib/newton-interpolation.ts
@@ -0,0 +1,247 @@
+import { math } from '@/lib/math';
+
+export type NewtonInterpolationMode = 'divided' | 'finite';
+export type NewtonDirection = 'forward' | 'backward';
+export type NewtonInputPoint = { x: string; y: string };
+export type NewtonPoint = { index: number; x: number; y: number };
+
+export interface NewtonDifferenceStep {
+ index: number;
+ order: number;
+ leftValue: number;
+ rightValue: number;
+ startX: number;
+ endX: number;
+ denominator: number | null;
+ result: number;
+}
+
+export interface NewtonEvaluationTerm {
+ order: number;
+ coefficient: number;
+ nodes: number[];
+ productAtTarget: number;
+ value: number;
+ cumulative: number;
+}
+
+export interface NewtonInterpolationResult {
+ mode: NewtonInterpolationMode;
+ direction: NewtonDirection | null;
+ spacing: number | null;
+ points: NewtonPoint[];
+ targetX: number;
+ differenceTable: number[][];
+ differenceSteps: NewtonDifferenceStep[];
+ coefficients: number[];
+ basisNodes: number[][];
+ evaluationTerms: NewtonEvaluationTerm[];
+ expandedCoefficients: number[];
+ interpolatedValue: number;
+}
+
+const MIN_POINTS = 2;
+const MAX_POINTS = 12;
+const MAX_INPUT_MAGNITUDE = 1e6;
+
+function parseReal(input: string): number {
+ try {
+ const value = math.evaluate(input);
+ if (
+ typeof value !== 'number' ||
+ !Number.isFinite(value) ||
+ Math.abs(value) > MAX_INPUT_MAGNITUDE
+ ) {
+ throw new Error('invalid');
+ }
+ return value;
+ } catch {
+ throw new Error('newtonInterpolation.err_real');
+ }
+}
+
+function factorial(value: number): number {
+ let result = 1;
+ for (let i = 2; i <= value; i += 1) result *= i;
+ return result;
+}
+
+function multiplyByLinearFactor(coefficients: number[], node: number): number[] {
+ const result = Array.from({ length: coefficients.length + 1 }, () => 0);
+ coefficients.forEach((coefficient, power) => {
+ result[power] -= coefficient * node;
+ result[power + 1] += coefficient;
+ });
+ return result;
+}
+
+function buildExpandedCoefficients(
+ coefficients: number[],
+ basisNodes: number[][],
+): number[] {
+ const expanded = Array.from({ length: coefficients.length }, () => 0);
+ coefficients.forEach((coefficient, index) => {
+ let basis = [1];
+ basisNodes[index].forEach((node) => {
+ basis = multiplyByLinearFactor(basis, node);
+ });
+ basis.forEach((value, power) => {
+ expanded[power] += coefficient * value;
+ });
+ });
+ const scale = Math.max(...expanded.map((value) => Math.abs(value)));
+ const tolerance = Number.EPSILON * scale * 16;
+ return expanded.map((value) => (Math.abs(value) <= tolerance ? 0 : value));
+}
+
+export function calculateNewtonInterpolation(
+ inputPoints: NewtonInputPoint[],
+ targetXInput: string,
+ mode: NewtonInterpolationMode,
+): NewtonInterpolationResult {
+ if (inputPoints.length < MIN_POINTS) {
+ throw new Error('newtonInterpolation.err_points_min');
+ }
+ if (inputPoints.length > MAX_POINTS) {
+ throw new Error('newtonInterpolation.err_points_max');
+ }
+
+ const points: NewtonPoint[] = inputPoints.map((point, index) => ({
+ index,
+ x: parseReal(point.x),
+ y: parseReal(point.y),
+ }));
+ if (new Set(points.map((point) => point.x)).size !== points.length) {
+ throw new Error('newtonInterpolation.err_duplicate');
+ }
+ const targetX = parseReal(targetXInput);
+ const count = points.length;
+ let spacing: number | null = null;
+ let direction: NewtonDirection | null = null;
+
+ if (mode === 'finite') {
+ spacing = points[1].x - points[0].x;
+ const equallySpaced = points.slice(1).every((point, index) => {
+ const step = point.x - points[index].x;
+ return Math.abs(step - spacing!) <= 1e-9 * Math.max(1, Math.abs(spacing!));
+ });
+ if (!equallySpaced) {
+ throw new Error('newtonInterpolation.err_spacing');
+ }
+ direction = Math.abs(targetX - points[0].x) <= Math.abs(targetX - points[count - 1].x)
+ ? 'forward'
+ : 'backward';
+ }
+
+ const differenceTable = Array.from({ length: count }, () =>
+ Array.from({ length: count }, () => 0),
+ );
+ points.forEach((point, index) => {
+ differenceTable[index][0] = point.y;
+ });
+
+ const differenceSteps: NewtonDifferenceStep[] = [];
+ const useBackwardDifferences = mode === 'finite' && direction === 'backward';
+ for (let order = 1; order < count; order += 1) {
+ const firstIndex = useBackwardDifferences ? order : 0;
+ const lastIndex = useBackwardDifferences ? count : count - order;
+ for (let index = firstIndex; index < lastIndex; index += 1) {
+ const leftValue = useBackwardDifferences
+ ? differenceTable[index][order - 1]
+ : differenceTable[index + 1][order - 1];
+ const rightValue = useBackwardDifferences
+ ? differenceTable[index - 1][order - 1]
+ : differenceTable[index][order - 1];
+ const numerator = leftValue - rightValue;
+ const denominator = mode === 'divided'
+ ? points[index + order].x - points[index].x
+ : null;
+ const value = denominator === null
+ ? numerator
+ : numerator / denominator;
+ if (!Number.isFinite(value)) {
+ throw new Error('newtonInterpolation.err_overflow');
+ }
+ differenceTable[index][order] = value;
+ differenceSteps.push({
+ index,
+ order,
+ leftValue,
+ rightValue,
+ startX: points[useBackwardDifferences ? index - order : index].x,
+ endX: points[useBackwardDifferences ? index : index + order].x,
+ denominator,
+ result: value,
+ });
+ }
+ }
+
+ const anchorIndex = mode === 'finite' && direction === 'backward'
+ ? count - 1
+ : 0;
+ const coefficients = [points[anchorIndex].y];
+ const basisNodes: number[][] = [[]];
+
+ for (let order = 1; order < count; order += 1) {
+ if (mode === 'divided') {
+ coefficients.push(differenceTable[0][order]);
+ basisNodes.push(points.slice(0, order).map((point) => point.x));
+ } else {
+ const rowIndex = direction === 'forward' ? 0 : count - 1;
+ const denominator = factorial(order) * Math.pow(spacing!, order);
+ coefficients.push(differenceTable[rowIndex][order] / denominator);
+ const nodes = direction === 'forward'
+ ? points.slice(0, order).map((point) => point.x)
+ : points.slice(count - order).map((point) => point.x).reverse();
+ basisNodes.push(nodes);
+ }
+ }
+
+ const evaluationTerms: NewtonEvaluationTerm[] = [];
+ let interpolatedValue = 0;
+ coefficients.forEach((coefficient, order) => {
+ const nodes = basisNodes[order];
+ const productAtTarget = nodes.reduce(
+ (product, node) => product * (targetX - node),
+ 1,
+ );
+ const value = coefficient * productAtTarget;
+ interpolatedValue += value;
+ if (![productAtTarget, value, interpolatedValue].every(Number.isFinite)) {
+ throw new Error('newtonInterpolation.err_overflow');
+ }
+ evaluationTerms.push({
+ order,
+ coefficient,
+ nodes,
+ productAtTarget,
+ value,
+ cumulative: interpolatedValue,
+ });
+ });
+
+ const expandedCoefficients = buildExpandedCoefficients(coefficients, basisNodes);
+ if (!expandedCoefficients.every(Number.isFinite)) {
+ throw new Error('newtonInterpolation.err_overflow');
+ }
+
+ return {
+ mode,
+ direction,
+ spacing,
+ points,
+ targetX,
+ differenceTable,
+ differenceSteps,
+ coefficients,
+ basisNodes,
+ evaluationTerms,
+ expandedCoefficients,
+ interpolatedValue,
+ };
+}
+
+export function formatNewtonValue(value: number, digits = 8): string {
+ if (!Number.isFinite(value)) return '—';
+ return Number(value.toPrecision(digits)).toString();
+}
diff --git a/public/images/og/newton-interpolation.png b/public/images/og/newton-interpolation.png
new file mode 100644
index 0000000000000000000000000000000000000000..dbf6f7909972fa23098be767ebb1db42d9cc9064
GIT binary patch
literal 108039
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
z