/* 28.05.2026: FFT form of Fourier and (reverse) Taylor series, Padé approximant, spline approximation and roots of polynomials by using global complex */ const CENTRE = 314, COLOUR = "#FFFFFF", LZ = Decimal.acos(-1), ZER = Complex(Decimal(0), Decimal(0)), ONE = Complex(Decimal(1), Decimal(0)); var canvas, com = 1, dif = 0, exp = 32, opt = 0, twice = CENTRE << 1; // ------------------------------------------------------------ // Parser: "gewohnte" Eingabe -> Complex-Auswertung ohne umständliche Syntax // Unterstützt: + - * / ^ (auch **), Klammern, implizite Multiplikation, // Funktionen: sin, cos, tan, exp, log/ln, sqrt, abs, atan, ... // Konstanten: pi, e, i // Variable: z // ------------------------------------------------------------ const COMPLEX_CONST = { pi: "Complex(Decimal.acos(-1), Decimal(0))", e: "Complex(Decimal(1).exp(), Decimal(0))", i: "Complex(Decimal(0), Decimal(1))" }; // Funktionsname (User) -> Complex-Prototyp-Methode (bh_bigcomplex.js) const FUNC_MAP = { sin: "csin", cos: "ccos", tan: "ctan", cot: "ccot", sec: "csec", csc: "ccsc", asin: "casin", acos: "cacos", atan: "catan", acot: "cacot", asec: "casec", acsc: "cacsc", sinh: "csinh", cosh: "ccosh", tanh: "ctanh", coth: "ccoth", sech: "csech", csch: "ccsch", asinh: "casinh", acosh: "cacosh", atanh: "catanh", acoth: "cacoth", asech: "casech", acsch: "cacsch", exp: "cexp", log: "clog", ln: "clog", sqrt: "csqrt", abs: "cabs" }; // Operatoren: Präzedenz/Assoziativität const OP_INFO = { "+": { prec: 10, assoc: "L", arity: 2 }, "-": { prec: 10, assoc: "L", arity: 2 }, "*": { prec: 20, assoc: "L", arity: 2 }, "/": { prec: 20, assoc: "L", arity: 2 }, "^": { prec: 30, assoc: "R", arity: 2 }, "u+": { prec: 25, assoc: "R", arity: 1 }, "u-": { prec: 25, assoc: "R", arity: 1 } }; function compileMathExprToJS(input) { const tokens0 = tokenize(input); const tokens1 = insertImplicitMultiplication(tokens0); const rpn = toRPN(tokens1); return compileRPNToJS(rpn); // liefert JS-Expr als String, Variable: zz (Complex) } function tokenize(s) { const tokens = []; let i = 0; const isWS = c => /\s/.test(c); const isDigit = c => /[0-9]/.test(c); const isIdentStart = c => /[A-Za-z_]/.test(c); const isIdent = c => /[A-Za-z0-9_]/.test(c); while (i < s.length) { const c = s[i]; if (isWS(c)) { i++; continue; } if ("+-*/^(),".includes(c)) { if (c === "*" && s[i + 1] === "*") { tokens.push({ type: "op", value: "^" }); i += 2; continue; } if (c === "(") tokens.push({ type: "lparen", value: "(" }); else if (c === ")") tokens.push({ type: "rparen", value: ")" }); else if (c === ",") tokens.push({ type: "comma", value: "," }); else tokens.push({ type: "op", value: c }); i++; continue; } // Zahl: 12, 12.3, .5, 1e-3, 1.2E+4 if (isDigit(c) || (c === "." && isDigit(s[i + 1] || ""))) { let j = i; while (isDigit(s[j] || "")) j++; if (s[j] === ".") { j++; while (isDigit(s[j] || "")) j++; } if (s[j] === "e" || s[j] === "E") { let k = j + 1; if (s[k] === "+" || s[k] === "-") k++; if (!isDigit(s[k] || "")) throw new Error("Ungültiger Exponent in Zahl bei Position " + j); while (isDigit(s[k] || "")) k++; j = k; } tokens.push({ type: "number", value: s.slice(i, j) }); i = j; continue; } // Identifier if (isIdentStart(c)) { let j = i + 1; while (isIdent(s[j] || "")) j++; const name = s.slice(i, j); tokens.push({ type: "ident", value: name }); i = j; continue; } throw new Error("Ungültiges Zeichen '" + c + "' bei Position " + i); } return tokens; } function isFunctionName(name) { return Object.prototype.hasOwnProperty.call(FUNC_MAP, name.toLowerCase()); } function insertImplicitMultiplication(tokens) { const out = []; const isValue = (t) => t.type === "number" || t.type === "ident" || t.type === "rparen"; const isValueStart = (t, prev) => { if (!t) return false; // ident '(' ist Funktionsaufruf => keine implizite Multiplikation if (t.type === "lparen" && prev && prev.type === "ident" && isFunctionName(prev.value)) return false; return t.type === "number" || t.type === "ident" || t.type === "lparen"; }; for (let i = 0; i < tokens.length; i++) { const t = tokens[i]; const prev = out[out.length - 1]; if (prev && isValue(prev) && isValueStart(t, prev)) out.push({ type: "op", value: "*" }); out.push(t); } return out; } function toRPN(tokens) { const output = []; const stack = []; const argCountStack = []; const peek = () => stack[stack.length - 1]; let prev = null; for (let i = 0; i < tokens.length; i++) { const t = tokens[i]; if (t.type === "number") { output.push(t); prev = t; continue; } if (t.type === "ident") { const name = t.value.toLowerCase(); const next = tokens[i + 1]; if (next && next.type === "lparen" && isFunctionName(name)) { stack.push({ type: "func", value: name }); prev = t; } else { output.push({ type: "ident", value: name }); prev = t; } continue; } if (t.type === "comma") { while (stack.length && peek().type !== "lparen") output.push(stack.pop()); if (!stack.length) throw new Error("Komma ohne passende Klammer."); if (!argCountStack.length) throw new Error("Komma außerhalb eines Funktionsaufrufs."); argCountStack[argCountStack.length - 1] += 1; prev = t; continue; } if (t.type === "lparen") { stack.push(t); const before = stack[stack.length - 2]; if (before && before.type === "func") argCountStack.push(0); prev = t; continue; } if (t.type === "rparen") { while (stack.length && peek().type !== "lparen") output.push(stack.pop()); if (!stack.length) throw new Error("Schließende Klammer ohne öffnende Klammer."); stack.pop(); // '(' if (stack.length && peek().type === "func") { const fn = stack.pop(); const commas = argCountStack.pop() ?? 0; output.push({ type: "func", value: fn.value, argc: commas + 1 }); } prev = t; continue; } if (t.type === "op") { let op = t.value; const prevIsOpOrStart = !prev || prev.type === "op" || prev.type === "lparen" || prev.type === "comma"; if ((op === "+" || op === "-") && prevIsOpOrStart) op = (op === "+") ? "u+" : "u-"; const o1 = OP_INFO[op]; if (!o1) throw new Error("Unbekannter Operator: " + op); while (stack.length) { const top = peek(); if (top.type !== "op") break; const o2 = OP_INFO[top.value]; if (!o2) break; // Prefix unary operators must not pop a preceding binary operator. // This keeps 2^-3 valid, while -z^2 becomes -(z^2). if (o1.arity === 1) break; const cond = (o1.assoc === "L" && o1.prec <= o2.prec) || (o1.assoc === "R" && o1.prec < o2.prec); if (!cond) break; output.push(stack.pop()); } stack.push({ type: "op", value: op }); prev = t; continue; } throw new Error("Interner Parserfehler bei Token: " + JSON.stringify(t)); } while (stack.length) { const t = stack.pop(); if (t.type === "lparen" || t.type === "rparen") throw new Error("Nicht geschlossene Klammer."); output.push(t); } return output; } function compileRPNToJS(rpn) { const st = []; const numToJS = (numStr) => `Complex(Decimal("${numStr}"), Decimal(0))`; const identToJS = (name) => { if (name === "z") return "zz"; if (COMPLEX_CONST[name]) return COMPLEX_CONST[name]; throw new Error("Unbekannte Variable/Konstante: " + name + " (erlaubt: z, pi, e, i)"); }; for (const t of rpn) { if (t.type === "number") { st.push(numToJS(t.value)); continue; } if (t.type === "ident") { st.push(identToJS(t.value)); continue; } if (t.type === "op") { const info = OP_INFO[t.value]; if (info.arity === 1) { const a = st.pop(); if (a === undefined) throw new Error("Operator " + t.value + ": Stack unterlaufen."); if (t.value === "u+") st.push(`(${a})`); else st.push(`(${a}).cneg()`); } else { const b = st.pop(), a = st.pop(); if (a === undefined || b === undefined) throw new Error("Operator " + t.value + ": Stack unterlaufen."); switch (t.value) { case "+": st.push(`(${a}).cadd(${b})`); break; case "-": st.push(`(${a}).csub(${b})`); break; case "*": st.push(`(${a}).cmul(${b})`); break; case "/": st.push(`(${a}).cdiv(${b})`); break; case "^": st.push(`(${a}).cpow(${b})`); break; default: throw new Error("Nicht implementierter Operator: " + t.value); } } continue; } if (t.type === "func") { const fn = t.value.toLowerCase(); const method = FUNC_MAP[fn]; if (!method) throw new Error("Unbekannte Funktion: " + fn); if (t.argc !== 1) throw new Error("Funktion " + fn + " erwartet genau ein Argument, erhalten: " + t.argc); const a = st.pop(); if (a === undefined) throw new Error("Funktion " + fn + ": Stack unterlaufen."); st.push(`(${a}).${method}()`); continue; } throw new Error("Interner Compilerfehler bei Token: " + JSON.stringify(t)); } if (st.length !== 1) throw new Error("Ausdruck ist nicht eindeutig (Rest-Stackgröße = " + st.length + ")."); return st[0]; } // Legacy: bisherige "Kettensyntax" weiter unterstützen (optional) function legacyExprToJS(expr) { // entspricht dem bisherigen check_function ohne affine Einbettung und ohne Test-eval let s = expr; s = s.replace(/\./g, ".c") .replace(/\[E\]/g, "Complex(Decimal(1).exp(),Decimal(0))") .replace(/\[I\]/g, "Complex(Decimal(0),Decimal(1))") .replace(/\[PI\]/g, "Complex(Decimal.acos(-1),Decimal(0))") + ".cadd([0])"; s = s.replace(/\[/g, "Complex(Decimal(").replace(/\]/g, "),Decimal(0))"); return s; } function isLegacySyntaxSafe(src) { // Prevent arbitrary JavaScript in legacy mode. // This still allows the old syntax: // z.add([1]).log(), [1].div([1].sub(z)), [PI], [E], [I], ... return /^[A-Za-z0-9_\.\(\)\[\]\+\-\*\/,Ee\s]+$/.test(src); } // Compiles the user function once to F(z)->Complex, including prefactor/shift. // Accepted input forms: // (A) conventional syntax: log(z+1), 1/(1-z), 2*i*exp(z), pi+e*z^3, ... // (B) legacy syntax: z.add([1]).log(), [1].div([1].sub(z)), ... function compileUserFunction(expr, prefac, pshift) { const src = (expr ?? "").toString().trim(); if (src.length === 0) return null; const looksLegacy = /[\[\]]|\b[zZ]\./.test(src); let core; if (looksLegacy) { if (!isLegacySyntaxSafe(src)) { throw new Error("Alte Syntax enthält nicht erlaubte Zeichen."); } const jsExpr = legacyExprToJS(src); core = new Function( "zz", "Complex", "Decimal", "const z = zz; return (" + jsExpr + ");" ); } else { const jsExpr = compileMathExprToJS(src); core = new Function( "zz", "Complex", "Decimal", "return (" + jsExpr + ");" ); } // Affine embedding as before: // f(z) -> f(prefac*z - pshift) // // Important: // Do not test at z=0 here. Functions such as sin(1/z), 1/z, // log(z), z/abs(z) may be valid for the chosen sampling path. return function (z) { const zz = z.cmul(prefac).csub(pshift); return core(zz, Complex, Decimal); }; } function decIsFinite(x) { if (!x) return false; if (typeof x.isFinite === "function") return x.isFinite(); if (typeof x.isNaN === "function" && x.isNaN()) return false; var n = x.toNumber(); return Number.isFinite(n); } function complexIsFinite(z) { return z && decIsFinite(z.re) && decIsFinite(z.im); } function safeEvalComplex(F, z) { try { var v = F(z); if (!complexIsFinite(v)) return null; return v; } catch (e) { return null; } } function complexMagnitude1(z) { return z.re.abs().add(z.im.abs()); } function makeReciprocalFunction(F) { // G(z) = 1/F(z). // A pole of F should become a zero of G. // // If F(z) blows up or cannot be evaluated exactly at the pole, // we interpret this as G(z) = 0 for the limiting root test. return function(z) { var v; try { v = F(z); } catch (e) { return ZER; } if (!complexIsFinite(v)) { return ZER; } // If F(z) is nearly zero, then 1/F is singular. // This is not a pole of F, but a zero of F. if (complexMagnitude1(v).lt(Decimal("1e-80"))) { return Complex(Decimal("1e80"), Decimal(0)); } return ONE.cdiv(v); }; } function verifyPoleCandidate(F, z) { // Heuristic check: // For a genuine pole, |F(z+h)| should grow as h -> 0. // This helps to avoid calling essential singularities or bad starts "poles". var h1 = Complex(Decimal("1e-3"), Decimal(0)); var h2 = Complex(Decimal("1e-4"), Decimal(0)); var h3 = Complex(Decimal("1e-5"), Decimal(0)); var v1 = safeEvalComplex(F, z.cadd(h1)); var v2 = safeEvalComplex(F, z.cadd(h2)); var v3 = safeEvalComplex(F, z.cadd(h3)); if (v1 === null || v2 === null || v3 === null) { return "Lokaler Pol-Check: nicht eindeutig, da die Funktionswerte in der Nähe nicht ausgewertet werden konnten.\n"; } var m1 = complexMagnitude1(v1); var m2 = complexMagnitude1(v2); var m3 = complexMagnitude1(v3); if (m2.gt(m1.mul(Decimal(3))) && m3.gt(m2.mul(Decimal(3)))) { return "Lokaler Pol-Check: die Werte in der Nähe steigen rapide an; dies ist mit einem Pol vereinbar.\n"; } return "Lokaler Pol-Check: es wurde kein eindeutiger Ausreißer festgestellt. Der Punkt könnte ein schwacher Pol,\nein schlechter Startpunkt, eine hebbare Singularität oder eine essentielle Singularität sein.\n"; } function build_form() { // build and write form var form = '
'; form += '
'; form += ''; form += '
 Beispiele   '; if (com == 1) form += ''; else form += ''; if (com == 2) form += '
'; else form += '
'; if (opt == 1) form += ''; else form += ''; form += ''; if (opt == 2) form += ''; else form += ''; if (opt == 3) form += '
'; else form += '
'; if (opt == 4) form += ''; else form += ''; if (opt == 5) form += ''; else form += ''; if (opt == 6) form += ''; else form += ''; if (opt == 7) form += '
'; else form += '
'; form += '. Grades   '; form += '. Stufe   '; form += '  -lb(Epsilon)   '; form += '. Ableitung'; form += '. Integral
'; form += ' Exponent'; form += ' Knoten'; form += ' Anzahl

'; form += '
'; form += ' Zählergrad: '; form += '
'; form += ' '; form += '
2
'; form += '
'; form += '
'; form += '
'; form += ' Nennergrad:'; form += '
'; form += ' '; form += '
2
'; form += '
'; form += '
'; form += ''; form += ''; form += ''; form += ''; form += ''; form += ''; form += ''; form += ''; form += ''; form += '
Realteil:
Realteil:
Realteil:
Realteil:
Realteil:'; form += '
'; document.writeln(form); canvas = new Raphael("canvas", twice, twice + 1); } function updateThumb(slider, thumbId) { if (!slider) return; const thumb = document.getElementById(thumbId); if (!thumb) return; const val = parseInt(slider.value, 10); const min = parseInt(slider.min, 10); const max = parseInt(slider.max, 10); if (!isFinite(val) || !isFinite(min) || !isFinite(max) || max === min) { thumb.textContent = slider.value; return; } const percent = (val - min) / (max - min); const sliderWidth = slider.offsetWidth || 1; const thumbWidth = 40; const offset = percent * (sliderWidth - thumbWidth); thumb.style.left = Math.max(0, offset) + "px"; thumb.textContent = val; } // Initialisierung beim Laden, damit die Blase nicht bei 0 startet function initSliders() { const s1 = document.getElementById('ell'); const s2 = document.getElementById('emm'); if(s1) updateThumb(s1, 'thumb-ell'); if(s2) updateThumb(s2, 'thumb-emm'); } // Aufruf nach dem Laden der Seite window.addEventListener('load', initSliders); // Und falls die Form dynamisch neu gebaut wird, auch nach build_form() aufrufen! // Initialisierung nach dem Laden der Seite window.addEventListener('DOMContentLoaded', (event) => { setTimeout(() => { updateThumb(document.getElementById('ell'), 'thumb-ell'); updateThumb(document.getElementById('emm'), 'thumb-emm'); }, 200); }); function solve_ls(mat) { // solve linear equation of rank d with coefficients in m using Gauss Jordan algorithm // m: array[d][d+1] // returns solution in m[i][d+1] // returns false if no solution found // // usage: // // EQ 1: a * x + b * y = c // EQ 2: e * x + f * y = g // // var m = [ [ a, b, c ], [ e, f, g ] ]; // if (solve_ls(m)) { // x = m[0][2]; // y = m[1][2]; // } else { // no solution // } const d = mat.length; for (var r = 0; r < d; r++) { if (mat[r][r].cisZero()) { var nzr = find_max(r); if (nzr == d) return false; for (var c = 0; c <= d; c++) [mat[r][c], mat[nzr][c]] = [mat[nzr][c], mat[r][c]]; // swap row } else { var piv = ONE.cdiv(mat[r][r]); // assert m[r][r] != 0 mat[r][r] = ONE; for (var c = r + 1; c <= d; c++) mat[r][c] = mat[r][c].cmul(piv); // norm row } for (var zr = 0; zr < d; zr++) if (zr != r && !mat[zr][r].cisZero()) pivot_cell(mat[zr][r].cneg()); } return true; function find_max(rv) { // find maximum in column for (var c = rv + 1, mi = Decimal(0), ma = d; c < d; c++) if (mat[c][rv].cabs().re.gt(mi)) [mi, ma] = [mat[c][rv].cabs().re, c]; return ma; } function pivot_cell(za) { // determine pivot mat[zr][r] = ZER; for (var c = r + 1; c <= d; c++) mat[zr][c] = mat[zr][c].cadd(za.cmul(mat[r][c])); } } function check_function(realft, z, prefac, pshift) { // Deprecated: Die Auswertung erfolgt nun über compileUserFunction / Parser. // Diese Funktion bleibt nur aus Kompatibilitätsgründen erhalten. try { var F = compileUserFunction(realft, prefac, pshift); F(z); } catch (exception) { return alert("Ausdruck ist nicht auswertbar!"); } return realft; } function compute_cpower(result, pbsize, factor) { result[0] = ONE; result[1] = factor; for (var k = 1; k <= pbsize / 2; k *= 2) for (var l = 1; l <= k; l++) result[k + l] = result[k].cmul(result[l]); } function compute_rpower(result, pbsize, factor) { result[0] = Decimal(1); result[1] = factor; for (var k = 1; k <= pbsize / 2; k *= 2) for (var l = 1; l <= k; l++) result[k + l] = result[k].mul( result[l]); } function initialise_tft(signal, pbsize, F, zvalue, method, epsilo) { var o = Decimal(0), t = new Complex((method <= 2) ? Decimal(epsilo) : Decimal(1/16384), o); var s = [], u = new Complex(o, LZ.mul(Decimal(2).div(Decimal(pbsize)))).cexp(), z = zvalue.cadd(t); compute_cpower(s, pbsize, u); for (var k = 0; k < pbsize; k++, z = zvalue.cadd(t.cmul(s[k]))) { try { signal[k] = F(z); } catch (exception) { signal[0] = null; return alert("Ausdruck ist nicht auswertbar!"); } } } function initialise_fft(signal, pbsize, F) { var e = Decimal(1e-17), o = Decimal(0), u = LZ.div(Decimal(pbsize)), x = new Complex(LZ.neg(), o), z = x; for (var k = 0; k < 2 * pbsize;) { try { signal[k] = F(z); } catch (exception) { signal[0] = null; return alert("Ausdruck ist nicht auswertbar!"); } if (signal[k].re.abs().lt(e) || signal[k].re.isNaN()) signal[k].re = o; if (signal[k].im.abs().lt(e) || signal[k].im.isNaN()) signal[k].im = o; z = x.cadd(Complex(u.mul(Decimal(++k)), o)); } } function compute_fft(ins) { const max = ins.length, one = Decimal(1), two = Decimal(2); // reverse bits for (var i = 0, j = 0, k, m = max >> 1; i < max - 1; i++, j += k) { if (i < j) [ins[i], ins[j]] = [ins[j], ins[i]]; for (k = m; k <= j; k >>= 1) j -= k; } // compute FFT for (var k = 0, l = 2, m = 1, c = ONE.cneg(), u = []; k < Math.log2(max); k++, m = l, l <<= 1) { compute_cpower(u, m, c); c = Complex(one.add(c.re).div(two).sqrt(), one.sub(c.re).div(two).sqrt().neg()); // do not negate for ifft, but normalise for (var j = 0; j < m; j++) { for (var i = j, n = j + m, t; i < max; i += l, n = i + m) { t = u[j].cmul(ins[n]); [ins[n], ins[i]] = [ins[i].csub(t), ins[i].cadd(t)]; } } } } function output_pade(result, factor, elldeg, emmdeg, numera, denomi) { var d, s, str = "Auf sechzehn Dezimalstellen gerundete Koeffizienten der Taylorpolynome von Zähler p und Nenner q:\n"; var z = Decimal(0), matrix = [], l = elldeg, m = emmdeg, n = l - m; if (factor > 0) for (var k = 0; k < 32; k++) result[k] = result[k + factor]; // build matrix for (var i = 0; i < m; i++) { matrix[i] = []; matrix[i][m] = result[++n + m].cneg(); for (var j = 0; j < m; j++) matrix[i][j] = (n + j < 0) ? ZER : result[n + j]; } // solve linear system and determine coefficients of quotient if (!solve_ls(matrix)) return str = "Das zugehörige lineare Gleichungssystem ist nicht lösbar: Bitte eventuell andere Vorgabe wählen."; denomi[0] = ONE; for (var j = 1; j <= m; j++) denomi[j] = matrix[m - j][m]; for (var i = 0; i <= l; i++) { d = result[i]; for (var j = 1; j <= Math.min(i, m); j++) d = d.cadd(denomi[j].cmul(result[i - j])); numera[i] = d; } // output quotient for (var k = 0; k <= l; k++) { s = (k < 10) ? "p[0" : "p["; str = str + s + k + "] = "; s = ((numera[k].re.gte(z)) ? "+" : "") + numera[k].re.toExponential(50, 4).replace("e", ")e"); s = s.padEnd(59); str = str + s.substring(0, 17) + "(" + s.substring(17); /* real coefficient */ s = ((numera[k].im.gte(z)) ? " +" : " ") + numera[k].im.toExponential(50, 4).replace("e", ")e"); str = str + s.substring(0, 17) + "(" + s.substring(17) + "im\n"; /* complex coefficient */ } for (var k = 0; k <= m; k++) { s = (k < 10) ? "q[0" : "q["; str = str + s + k + "] = "; s = ((denomi[k].re.gte(z)) ? "+" : "") + denomi[k].re.toExponential(50, 4).replace("e", ")e"); s = s.padEnd(59); str = str + s.substring(0, 17) + "(" + s.substring(17); /* real coefficient */ s = ((denomi[k].im.gte(z)) ? " +" : " ") + denomi[k].im.toExponential(50, 4).replace("e", ")e"); str = str + s.substring(0, 17) + "(" + s.substring(17) + "im\n"; /* complex coefficient */ } return str; } function output_taylor(result, realft, factor, intnum, ulimit, llimit, nummer, F, rootReport) { var o = Decimal(0), v = ZER, d = ulimit.csub(llimit).cdiv(new Complex(Decimal(intnum), o)), p = (factor > 0) ? factor + 32 : 32, s; var b = [], l = [], u = [], w = ZER, z = nummer, str = "Auf sechzehn Dezimalstellen gerundete Koeffizienten der Taylorreihe:\n"; rootReport = rootReport || ""; b[0] = llimit; for (var j = 0; j < intnum; j++) { l[j] = u[j] = result[p]; b[j + 1] = b[j].cadd(d); } for (var j = 0; j < intnum; j++) { for (var k = p - 1; k >= Math.max(0, factor); k--) { u[j] = u[j].cmul(b[j + 1]).cadd(result[k]); l[j] = l[j].cmul(b[j] ).cadd(result[k]); } } // output coefficients for (var k = Math.max(0, factor); k < p; k++) { s = (k < factor + 10) ? "a[0" : "a["; str = str + s + (k - factor) + "] = "; s = ((result[k].re.gte(o)) ? "+" : "") + result[k].re.toExponential(50, 4).replace("e", ")e"); s = s.padEnd(59); str = str + s.substring(0, 17) + "(" + s.substring(17); /* real coefficient */ s = ((result[k].im.gte(o)) ? " +" : " ") + result[k].im.toExponential(50, 4).replace("e", ")e"); str = str + s.substring(0, 17) + "(" + s.substring(17) + "im\n"; /* complex coefficient */ } if (factor < 0) { d = new Complex(Decimal(-factor), o); for (var j = 0; j < intnum; j++) { u[j] = u[j].cmul(b[j + 1].cpow(d)); l[j] = l[j].cmul(b[j ].cpow(d)); } } if (rootReport !== "") { str = str + rootReport + "\n"; } // output further information (e. g. for integration) for (var j = 0; j < intnum; j++) [v, w] = [v.cadd(u[j]), w.cadd(l[j])]; str = str + "Die Summe der oberen Werte beträgt:\n"; s = ((v.re.gte(o)) ? "+" : "") + v.re.toExponential(50, 4).replace("e", ")e"); s = s.padEnd(59); str = str + s.substring(0, 17) + "(" + s.substring(17); s = ((v.im.gte(o)) ? " +" : " ") + v.im.toExponential(50, 4).replace("e", ")e"); str = str + s.substring(0, 17) + "(" + s.substring(17) + "im\n"; str = str + "Die Summe der unteren Werte beträgt:\n"; s = ((w.re.gte(o)) ? "+" : "") + w.re.toExponential(50, 4).replace("e", ")e"); s = s.padEnd(59); str = str + s.substring(0, 17) + "(" + s.substring(17); s = ((w.im.gte(o)) ? " +" : " ") + w.im.toExponential(50, 4).replace("e", ")e"); str = str + s.substring(0, 17) + "(" + s.substring(17) + "im\n"; d = v.csub(w); str = str + "Die Differenz der Werte beträgt:\n"; s = ((d.re.gte(o)) ? "+" : "") + d.re.toExponential(50, 4).replace("e", ")e"); s = s.padEnd(59); str = str + s.substring(0, 17) + "(" + s.substring(17); s = ((d.im.gte(o)) ? " +" : " ") + d.im.toExponential(50, 4).replace("e", ")e"); str = str + s.substring(0, 17) + "(" + s.substring(17) + "im\n"; // output zero of function and the value of the latter there if (isNaN(nummer)) { str = str + "Die berechnete Nullstelle liegt bei:\n"; s = ((nummer.re.gte(o)) ? "+" : "") + nummer.re.toExponential(50, 4).replace("e", ")e"); s = s.padEnd(59); str = str + s.substring(0, 17) + "(" + s.substring(17); s = ((nummer.im.gte(o)) ? " +" : " ") + nummer.im.toExponential(50, 4).replace("e", ")e"); str = str + s.substring(0, 17) + "(" + s.substring(17) + "im\n"; z = F(z); str = str + "Der Funktionswert an der berechneten Nullstelle ist:\n"; s = ((z.re.gte(o)) ? "+" : "") + z.re.toExponential(50, 4).replace("e", ")e"); s = s.padEnd(59); str = str + s.substring(0, 17) + "(" + s.substring(17); s = ((z.im.gte(o)) ? " +" : " ") + z.im.toExponential(50, 4).replace("e", ")e"); str = str + s.substring(0, 17) + "(" + s.substring(17) + "im\n"; } return str; } function output_spline(spline, values, degree, knotno) { var s, str = "\nAuf sechzehn Dezimalstellen gerundete Koeffizienten der Splines:", z = Decimal(0); for (var i = 0; i < knotno; i++) { str = str + "\nKnoten " + (i + 1) + " bei "; s = ((values[i].re.gte(z)) ? "+" : "") + values[i].re.toExponential(50, 4).replace("e", ")e"); s = s.padEnd(59); str = str + s.substring(0, 17) + "(" + s.substring(17).trimEnd() + ":\n"; /* real coefficient */ for (var j = 0; j <= degree; j++) { str = str + "s[" + (i + 1) + "][" + j + "] = "; s = ((spline[i][j].re.gte(z)) ? "+" : "") + spline[i][j].re.toExponential(50, 4).replace("e", ")e"); s = s.padEnd(59); str = str + s.substring(0, 17) + "(" + s.substring(17); /* real coefficient */ s = ((spline[i][j].im.gte(z)) ? " +" : " ") + spline[i][j].im.toExponential(50, 4).replace("e", ")e"); str = str + s.substring(0, 17) + "(" + s.substring(17) + "im\n"; /* complex coefficient */ } } return str; } function compute_taylor(result, pbsize, factor, epsilo) { var z = Decimal(0), n = 32, m = (factor > 0) ? n + factor : n, e = Decimal(epsilo), p = [], q = [], o = Decimal(1), f = o, h = new Complex(Decimal(pbsize), z); compute_rpower(p, n, e); for (var k = 1; k <= -factor; k++) f = f.mul(Decimal(k)); if (factor < 0) f = o.div(f); result[0] = (factor > 0) ? ZER : result[0].cdiv(h).cmul(Complex(f, z)); for (var k = 1; k < n; k++) result[k] = result[k].cdiv(h.cmul(Complex(Decimal(p[k]), z))); if (factor == 0) return; // differentiate or integrate if (factor > 0) compute_rpower(q, Math.pow(2, Math.ceil(Math.log2(factor))), e); for (var k = n; k < m; k++) result[k] = result[k].cdiv(h.cmul(Complex(p[n] * q[k - n], z))); for (var k = 1; k < m; k++) { f = f.mul(Decimal(k)); if (k > factor) f = f.div(Decimal(k - factor)); result[k] = (k < factor) ? ZER : result[k].cmul(Complex(f, z)); } } function compute_reverse(result, pbsize, vpoint, epsilo) { var z = Decimal(0), r = new Complex(z, LZ.mul(Decimal(2).div(Decimal(pbsize)))).cexp(), x; var j = 1, e = Decimal(epsilo), f, h = new Complex(e, z), p = [], q = [], t = [], u = []; if (result[1].cabs().re.lt(Decimal(1E-25))) alert("Die Umkehrung existiert nicht wegen a[1] = 0!"); // build quotients by Horner scheme compute_rpower(p, pbsize, e); compute_cpower(u, pbsize, ONE.cdiv(r)); for (var k = 0; k < pbsize; k++) { x = h.cdiv(u[k]); f = result[pbsize - 1]; for (var l = pbsize - 2; l > 0; l--) f = f.cmul(x).cadd(result[l]); t[k] = []; compute_cpower(t[k], pbsize, ONE.cdiv(f)); } // revert Taylor series result[0] = vpoint; f = ZER; for (var k = 0; k < pbsize; k++) f = f.cadd(t[k][1]); result[1] = f.cdiv(Complex(Decimal(pbsize), z)); for (var l = 2; l < pbsize; l++) { compute_cpower(q, pbsize, u[j]); f = t[0][l]; for (var k = 1; k < pbsize; k++) f = f.cadd(t[k][l].cmul(q[k])); result[l] = f.cdiv(Complex(Decimal(pbsize * l).mul(p[j++]), z)); } } function compute_fourier(result, pbsize, differ) { var str = "Auf sechzehn Dezimalstellen gerundete Koeffizienten der Fourierreihe:\n", o = Decimal(0); var f = new Complex(Decimal(differ), o), h = new Complex(Decimal(pbsize), o), n = pbsize / 2, s; // differentiate if necessary for (var j = -n; j < n; j++) { k = (j < 0) ? j + pbsize : j; result[k] = result[k].cdiv(h).cmul(Complex(o, Decimal(Math.round(j))).cpow(f)); h = h.cneg(); s = (j < 0) ? "c[" : "c[ "; if (Math.abs(j) < 10) s = s + " "; str = str + s + j + "] = "; s = ((result[k].re.gte(o)) ? "+" : "") + result[k].re.toExponential(50, 4).replace("e", ")e"); s = s.padEnd(59); str = str + s.substring(0, 17) + "(" + s.substring(17); /* real coefficient */ s = ((result[k].im.gte(o)) ? " +" : " ") + result[k].im.toExponential(50, 4).replace("e", ")e"); str = str + s.substring(0, 17) + "(" + s.substring(17) + "im\n"; /* complex coefficient */ } return str; } function compute_simpson(result, pbsize, zvalue, conver, F) { var c0 = ZER, c1 = ONE, str = "Auf sechzehn Dezimalstellen gerundete Iterationen:\n", b = 16384, d = 1e-40; var fa = [], ga = [], ha = [], height = [], e, f, g, h, m, o = Decimal(0), p, v = c0, w, x, y, z = zvalue; var c2 = new Complex(Decimal(2), o), c3 = new Complex(Decimal(3), o); var c4 = new Complex(Decimal(4), o), c5 = new Complex(Decimal(5), o); var c6 = new Complex(Decimal(6), o), c7 = new Complex(Decimal(7), o); var c8 = new Complex(Decimal(b), o), c9 = new Complex(o, Decimal(d)); // determine coefficients by differentiating Taylor series height[0] = new Complex(Decimal(1 / pbsize), o); result[0] = result[0].cmul(height[0]); for (var k = 1; k < pbsize; k++) { height[k] = height[k - 1].cmul(c8); result[k] = result[k].cmul(height[k]); fa[k] = result[k].cmul(Complex(Decimal(k), o)); ga[k] = fa[k].cmul(Complex(Decimal(k - 1), o)); ha[k] = ga[k].cmul(Complex(Decimal(k - 2), o)); } pbsize--; // determine derivatives by using Horner's scheme for (m = 1; m < 17; m++) { e = z; f = g = h = x = c0; var k = pbsize; do { f = f.cmul(v).cadd( fa[k]); x = x.cmul(v).cadd(result[k]); } while (--k > 0); x = x.cmul(v).cadd(result[0]); // precise and fast, quadratic, cubic, quartic, quintic, sextic, septic, and octic convergence switch(conver) { case 0: y = z; z = y.cadd(c9); x = F(z); z = y.csub(c9); f = F(z); z = c1.csub( x.cdiv(f)); z = c2.cdiv(z).csub(c1); v = v.cadd( z.cmul(c9)); break; case 1: y = z; z = y.cadd(c9); x = F(z); z = y.csub(c9); f = F(z); z = c1.csub( x.cdiv(f)); z = c2.cdiv(z).csub(c1); w = z.cmul(c9); x = x.cadd( f).cdiv(c2); f = x.csub( f).cdiv(c9); // see case 8 z = y.cadd(w); g = F(z); y = w.cmul(g); w = x.csub(g.cmul(c2)); z = z.cadd(y.cdiv(w )); h = F(z); w = x.csub(g).cdiv( w); y = h.cdiv( g.csub(h)); w = w.cmul(w).cadd( y); y = h.cmul(c4); y = y.cdiv( x.cadd(h)); y = y.cadd( w); y = y.cmul( h.cdiv(f)); y = y.cadd(zvalue); v = z.csub( y); break; case 2: v = v.csub( x.cdiv(f)); break; case 3: k = pbsize; do { g = g.cmul(v).cadd( ga[k]); } while (--k > 1); g = g.cdiv(c2.cmul(f)); g = g.csub( f.cdiv(x)); v = v.cadd(c1.cdiv(g)); break; case 4: k = pbsize; do { g = g.cmul(v).cadd( ga[k]); h = h.cmul(v).cadd( ha[k]); } while (--k > 2); g = g.cmul(v).cadd( ga[2]); g = g.cmul(x); z = f.cmul(f); y = z.csub(g).cmul(c2); g = g.cadd(y); f = f.cmul( y.cdiv(g)); y = x.cdiv(z).cdiv(c6); y = y.cmul(h); y = f.cdiv(x).cadd(y ); v = v.csub(c1.cdiv(y)); break; case 5: z = v.csub( x.cdiv(f)); k = pbsize; do { g = g.cmul(z).cadd(result[k]); h = h.cmul(z).cadd( fa[k]); } while (--k > 0); g = g.cmul(z).cadd(result[0]); h = h.cmul(h ); g = g.cdiv(f ); f = f.cmul(f ); y = f.cmul(c5); y = y.cadd(c3.cmul(h)); x = f.cadd(c7.cmul(h)); y = y.cdiv(x).cmul(g ); v = z.csub(y ); break; case 6: z = v.csub( x.cdiv(f)); k = pbsize; do { g = g.cmul(z).cadd( fa[k]); } while (--k > 0); z = x.cdiv( f.cadd(g)); z = v.csub(c2.cmul(z)); k = pbsize; do { h = h.cmul(z).cadd(result[k]); } while (k-- > 0); y = c3.cmul(g).csub(f); y = f.cadd(g).cdiv(y); y = h.cdiv(f).cmul(y); v = z.csub(y); break; case 7: y = x.cdiv(f); z = v.csub(y); k = pbsize; do { g = g.cmul(z).cadd(result[k]); } while (k-- > 0); y = y.cmul(g); w = x.csub(g.cmul(c2)); z = z.csub(y.cdiv( w)); k = pbsize; do { h = h.cmul(z).cadd(result[k]); } while (k-- > 0); x = x.csub(g).cdiv( w); y = h.cdiv( g.csub(h)); x = x.cmul(x).cadd( y); x = x.cdiv(f).cmul( h); v = z.csub(x); break; case 8: y = x.cdiv(f); z = v.csub(y); k = pbsize; do { g = g.cmul(z).cadd(result[k]); } while (k-- > 0); y = y.cmul(g); w = x.csub(g.cmul(c2)); z = z.csub(y.cdiv(w )); k = pbsize; do { h = h.cmul(z).cadd(result[k]); } while (k-- > 0); w = x.csub(g).cdiv( w); y = h.cdiv( g.csub(h)); w = w.cmul(w).cadd( y); y = h.cmul(c4); y = y.cdiv( x.cadd(h)); y = y.cadd( w); y = y.cmul( h.cdiv(f)); v = z.csub( y); break; default : } p = (m < 10) ? " " + m : m; z = v.cadd(zvalue); // output if interesting s = ((z.re.gte(o)) ? "+" : "") + z.re.toExponential(16, 4); str = str + p + ": Re(z) = " + s.padEnd(24) + " (" + ((z.re.gte(o)) ? "+" : "") + z.re.toExponential(80, 4) + ")\n"; s = ((z.im.gte(o)) ? "+" : "") + z.im.toExponential(16, 4); str = str + "und Im(z) = " + s.padEnd(24) + " (" + ((z.im.gte(o)) ? "+" : "") + z.im.toExponential(80, 4) + ")\n"; if (z.re.isNaN() || z.im.isNaN() || conver < 2) m = 17; } str = (m == 18) ? "Die Berechnung wurde vorzeitig abgebrochen; bitte versuchen Sie es mit einer anderen Eingabe." : str + "|" + z.re.toString() + "|" + z.im.toString(); return str; } // ============================================================================ // START: Komplexe Nullstellen- und Polextraktion (Toleranzschlauch) // ============================================================================ /** * Scannt einen Streifen um die reelle Achse [-π, π] nach komplexen Nullstellen. * Identifiziert lokale Minima des Betrags, um verborgene Singularitäten aufzuspüren. */ function findRootsInStrip(F, isReciprocal, steps = 1000) { let roots = []; let start = LZ.neg(), end = LZ; let stepSize = end.sub(start).div(Decimal(steps)); let mags = [], xs = []; // Vorab-Scan der reellen Achse, um lokale Minima des Betrags zu lokalisieren for (let i = 0; i <= steps; i++) { let x = start.add(stepSize.mul(Decimal(i))); xs.push(x); let val = F(Complex(x, Decimal(0))); if (isReciprocal) val = (val.re.abs().lt(1e-100) && val.im.abs().lt(1e-100)) ? Complex(Decimal(1e100), Decimal(0)) : ONE.cdiv(val); mags.push(val.re.abs().add(val.im.abs())); // 1-Norm } for (let i = 1; i < steps; i++) { let mag = mags[i]; // Volltreffer oder lokales Minimum, das auf eine nahegelegene komplexe Nullstelle hinweist let isDirectHit = mag.lt(Decimal("1e-8")); let isLocalMin = (mag.lt(mags[i - 1]) && mag.lt(mags[i + 1]) && mag.lt(Decimal("0.5"))); if (isDirectHit || isLocalMin) { // Leichte Verschiebung in die komplexe Ebene, um f'(x)=0 Sattelpunkte zu umgehen let z0 = Complex(xs[i], Decimal("1e-5")); let rootZ = refineComplexRoot(F, isReciprocal, z0); // Filtere nach Nullstellen innerhalb des festgelegten Toleranzschlauchs (|Im| < 1.0) if (rootZ !== null && rootZ.im.abs().lt(Decimal("1.0"))) { let isNew = true; for (let r of roots) { if (r.csub(rootZ).re.abs().add(r.csub(rootZ).im.abs()).lt(Decimal("1e-6"))) isNew = false; } if (isNew) roots.push(rootZ); // Prüfe auf die konjugiert komplexe Nullstelle let conjZ = Complex(rootZ.re, rootZ.im.neg()); let conjRoot = refineComplexRoot(F, isReciprocal, conjZ); if (conjRoot !== null && conjRoot.im.abs().lt(Decimal("1.0"))) { let isNewConj = true; for (let r of roots) { if (r.csub(conjRoot).re.abs().add(r.csub(conjRoot).im.abs()).lt(Decimal("1e-6"))) isNewConj = false; } if (isNewConj) roots.push(conjRoot); } } } } return roots; } /** * Verfeinerung durch komplexes Newton-Verfahren */ function refineComplexRoot(F, isReciprocal, z0) { let z = z0, h = Complex(Decimal("1e-7"), Decimal(0)), tol = Decimal("1e-12"); for (let i = 0; i < 50; i++) { let val = F(z); if (isReciprocal) val = (val.re.abs().lt(1e-100) && val.im.abs().lt(1e-100)) ? Complex(Decimal(1e100), Decimal(0)) : ONE.cdiv(val); let magnitude = val.re.abs().add(val.im.abs()); if (magnitude.lt(tol)) return z; let valH = F(z.cadd(h)); if (isReciprocal) valH = (valH.re.abs().lt(1e-100) && valH.im.abs().lt(1e-100)) ? Complex(Decimal(1e100), Decimal(0)) : ONE.cdiv(valH); let df = valH.csub(val).cdiv(h); // Abbruch, falls die Ableitung verschwindet, um Division durch Null zu verhindern if (df.re.abs().add(df.im.abs()).lt(Decimal("1e-18"))) return null; z = z.csub(val.cdiv(df)); } return null; } // ============================================================================ // ENDE: Komplexe Nullstellen- und Polextraktion // ============================================================================ /** * Generiert die reelle, deflationierte Funktion durch direkte Produktdivision. * Die Funktion übernimmt ein reelles x (als Decimal) und liefert den * deflatierten Funktionswert für die Kronreihenentwicklung. * @param {Function} F - Ursprungsfunktion * @param {Array} rootsF - Nullstellen von f(x) * @param {Array} rootsG - Nullstellen von 1/f(x) * @returns {Function} */ function createDeflatedRealFunctionProduct(F, rootsF, rootsG) { return function(x) { // x muss zwingend ein Decimal-Objekt sein let tol = Decimal("1e-11"); let h = Decimal("1e-7"); // Prüfen, ob x kritisch nah an einer Singularität liegt let nearSingularity = false; for (let r of rootsF) if (x.sub(r).abs().lt(tol)) nearSingularity = true; for (let p of rootsG) if (x.sub(p).abs().lt(tol)) nearSingularity = true; // Implizite L'Hôpital-Grenzwertbildung durch minimalen Shift let evalX = nearSingularity ? x.add(h) : x; let val = F(Complex(evalX, Decimal(0))).re; // Zähler-Dynamik (Nullstellen von f) let prodF = Decimal(1); for (let r of rootsF) prodF = prodF.mul(evalX.sub(r)); // Nenner-Dynamik (Polstellen von f / Nullstellen von g) let prodG = Decimal(1); for (let p of rootsG) prodG = prodG.mul(evalX.sub(p)); // Rationale Bereinigung in einem Schritt return val.mul(prodG).div(prodF); }; } // ============================================================================ // ENDE: Reelle Null- und Polstellen-Abspaltung // ============================================================================ // ============================================================================ // START: Nullstellen des Taylorpolynoms per Kron-DFT-Aberth // ============================================================================ function complex_abs_dec(z) { if (!z) return Decimal(0); return z.cabs().re; } function complex_is_tiny(z, tol) { return complex_abs_dec(z).lt(tol); } function fmt_dec16(x) { return ((x.gte(Decimal(0))) ? "+" : "") + x.toExponential(16, 4); } function fmt_complex16(z) { return fmt_dec16(z.re) + " " + fmt_dec16(z.im) + "im"; } function next_power_two_int_gt(n) { var k = 1; while (k <= n) k <<= 1; return Math.max(8, k); } function next_power_two_decimal(x) { var xn = x.toNumber(); if (!Number.isFinite(xn) || xn <= 1.0) return Decimal(1); var e = Math.ceil(Math.log2(xn)); if (e < 0) e = 0; if (e > 4096) e = 4096; var y = Decimal(1); var two = Decimal(2); for (var k = 0; k < e; k++) y = y.mul(two); return y; } function extract_taylor_coeffs_for_roots(result, factor) { // Verwendet die in output_taylor tatsächlich ausgegebenen Koeffizienten. // Für die gewöhnliche Taylorreihe ist factor = 0. var first = Math.max(0, factor); var coeffs = []; for (var k = 0; k < 32; k++) { coeffs[k] = result[first + k] || ZER; } return coeffs; } function effective_poly_degree(coeffs) { var maxabs = Decimal(0); for (var k = 0; k < coeffs.length; k++) { var a = complex_abs_dec(coeffs[k]); if (a.gt(maxabs)) maxabs = a; } if (maxabs.lte(Decimal(0))) return 0; var tol = maxabs.mul(Decimal("1e-26")); if (tol.lt(Decimal("1e-60"))) tol = Decimal("1e-60"); for (var k = coeffs.length - 1; k >= 1; k--) { if (complex_abs_dec(coeffs[k]).gt(tol)) return k; } return 0; } function cauchy_radius_for_local_coeffs(coeffs, N) { if (N < 1) return Decimal(1); var lead = complex_abs_dec(coeffs[N]); if (lead.lte(Decimal(0))) return Decimal(1); var m = Decimal(0); for (var j = 0; j < N; j++) { var a = complex_abs_dec(coeffs[j]); if (a.gt(m)) m = a; } return Decimal(1).add(m.div(lead)); } function poly_eval_coeffs(coeffs, z, N) { var p = coeffs[N] || ZER; for (var k = N - 1; k >= 0; k--) { p = p.cmul(z).cadd(coeffs[k] || ZER); } return p; } function poly_eval_deriv_coeffs(coeffs, z, N) { var p = coeffs[N] || ZER; var dp = ZER; for (var k = N - 1; k >= 0; k--) { dp = dp.cmul(z).cadd(p); p = p.cmul(z).cadd(coeffs[k] || ZER); } return { p: p, dp: dp }; } function scaled_poly_residual(coeffs, z, N) { var p = poly_eval_coeffs(coeffs, z, N); var az = complex_abs_dec(z); var pow = Decimal(1); var den = Decimal(0); for (var j = 0; j <= N; j++) { den = den.add(complex_abs_dec(coeffs[j] || ZER).mul(pow)); pow = pow.mul(az); } if (den.lte(Decimal(0))) den = Decimal(1); return complex_abs_dec(p).div(den); } function kron_dft_coeffs_from_taylor(coeffs, N, rho, K) { // Lokales Taylorpolynom: // T(h) = a_0 + ... + a_N h^N // // Kronform: // h = rho*q, |q| = 1 // P(q) = T(rho*q) = d_0 + ... + d_N q^N // // Die d_j werden bewusst per direkter DFT aus den Kronwerten rekonstruiert. // Damit entspricht die Erweiterung dem Kron-DFT-Aberth-Verfahren. var samples = []; var d = []; var upow = []; var zero = Decimal(0); var rhoC = Complex(rho, zero); var u = Complex( zero, LZ.mul(Decimal(2).div(Decimal(K))) ).cexp(); compute_cpower(upow, K, u); for (var k = 0; k < K; k++) { var h = rhoC.cmul(upow[k]); samples[k] = poly_eval_coeffs(coeffs, h, N); } var invK = ONE.cdiv(Complex(Decimal(K), zero)); for (var j = 0; j <= N; j++) { var s = ZER; for (var k = 0; k < K; k++) { var idx = (K - ((j * k) % K)) % K; s = s.cadd(samples[k].cmul(upow[idx])); } d[j] = s.cmul(invK); } return d; } function aberth_roots_coeffs(coeffs, N) { var roots = []; var zero = Decimal(0); var oneD = Decimal(1); var tiny = Decimal("1e-60"); var RA = cauchy_radius_for_local_coeffs(coeffs, N); if (RA.lt(oneD)) RA = oneD; // Halber Winkelschritt gegen einfache Symmetrie-Sonderfälle. for (var i = 0; i < N; i++) { var ang = 2.0 * Math.PI * (i + 0.5) / N; roots[i] = Complex( RA.mul(Decimal(Math.cos(ang))), RA.mul(Decimal(Math.sin(ang))) ); } var tolStep = Decimal("1e-20"); var tolRes = Decimal("1e-20"); var maxIter = 100; var minIter = 5; var maxStep = Decimal(0); var maxRes = Decimal(0); for (var it = 0; it < maxIter; it++) { var next = []; maxStep = Decimal(0); maxRes = Decimal(0); for (var i = 0; i < N; i++) { var ev = poly_eval_deriv_coeffs(coeffs, roots[i], N); var p = ev.p; var dp = ev.dp; var res = scaled_poly_residual(coeffs, roots[i], N); if (res.gt(maxRes)) maxRes = res; if (complex_abs_dec(dp).lt(tiny)) { // Sehr flache Stelle: minimal stören statt Division durch fast Null. next[i] = roots[i].cadd(Complex(tiny, tiny)); continue; } var delta = p.cdiv(dp); var rep = ZER; for (var j = 0; j < N; j++) { if (j === i) continue; var diff = roots[i].csub(roots[j]); if (complex_abs_dec(diff).lt(tiny)) { diff = diff.cadd(Complex(tiny, tiny)); } rep = rep.cadd(ONE.cdiv(diff)); } var denom = ONE.csub(delta.cmul(rep)); if (complex_abs_dec(denom).lt(tiny)) { denom = denom.cadd(Complex(tiny, tiny)); } var step = delta.cdiv(denom); next[i] = roots[i].csub(step); var st = complex_abs_dec(step); if (st.gt(maxStep)) maxStep = st; } roots = next; var maxRoot = Decimal(1); for (var i = 0; i < N; i++) { var ar = complex_abs_dec(roots[i]); if (ar.gt(maxRoot)) maxRoot = ar; } if (it >= minIter && maxStep.lte(tolStep.mul(maxRoot)) && maxRes.lte(tolRes)) { break; } } roots.sort(function(a, b) { var dr = a.re.sub(b.re).toNumber(); if (Math.abs(dr) > 1e-14) return dr; return a.im.sub(b.im).toNumber(); }); return { roots: roots, maxStep: maxStep, maxRes: maxRes }; } function output_taylor_roots_from_coeffs(result, factor, centre, F) { var coeffs = extract_taylor_coeffs_for_roots(result, factor); var N = effective_poly_degree(coeffs); var str = ""; var rootsZ = []; str += "\nNullstellen des Taylorpolynoms\n"; str += "--------------------------------\n"; if (N < 1) { str += "Das Taylorpolynom besitzt nach der aktuellen Trunkation keinen positiven Grad.\n"; return { text: str, rootsZ: rootsZ }; } var RC = cauchy_radius_for_local_coeffs(coeffs, N); var rho = next_power_two_decimal(RC); var K = next_power_two_int_gt(2 * N); var d = kron_dft_coeffs_from_taylor(coeffs, N, rho, K); var sol = aberth_roots_coeffs(d, N); var rhoC = Complex(rho, Decimal(0)); str += "Berechnet werden die Nullstellen des abgeschnittenen Taylorpolynoms\n"; str += "T(h)=a_0+a_1*h+...+a_N*h^N mit h=z-z_0.\n\n"; str += "Entwicklungspunkt z_0 = " + fmt_complex16(centre) + "\n"; str += "effektiver Grad N = " + N + "\n"; str += "Cauchy-Radius R_C = " + RC.toExponential(16, 4) + "\n"; str += "Kronradius rho = " + rho.toExponential(16, 4) + "\n"; str += "Kronpunkte K = " + K + "\n"; str += "max. skaliertes Res. = " + sol.maxRes.toExponential(16, 4) + "\n\n"; str += "Gefundene Nullstellen z_i = z_0 + rho*q_i:\n"; var maxT = Decimal(0); var maxF = Decimal(0); for (var i = 0; i < sol.roots.length; i++) { var q = sol.roots[i]; var h = rhoC.cmul(q); var z = centre.cadd(h); rootsZ.push(z); var tv = poly_eval_coeffs(coeffs, h, N); var at = complex_abs_dec(tv); if (at.gt(maxT)) maxT = at; var fv = safeEvalComplex(F, z); if (fv !== null) { var af = complex_abs_dec(fv); if (af.gt(maxF)) maxF = af; } str += "z_" + (i + 1) + " = " + fmt_complex16(z) + "\n"; } str += "\nKontrolle am Taylorpolynom:\n"; str += "max. |T(z_i-z_0)| = " + maxT.toExponential(16, 4) + "\n"; str += "\nHinweis:\n"; str += "Dies sind Nullstellen des abgeschnittenen Taylorpolynoms. Sie sind nur dann\n"; str += "auch gute Nullstellen der Originalfunktion, wenn das Taylorpolynom im betreffenden\n"; str += "Gebiet eine hinreichend gute Näherung der Funktion ist.\n"; if (maxF.gt(Decimal(0))) { str += "max. |F(z_i)| = " + maxF.toExponential(16, 4) + "\n"; } return { text: str, rootsZ: rootsZ }; } // ============================================================================ // ENDE: Nullstellen des Taylorpolynoms per Kron-DFT-Aberth // ============================================================================ function compute_coeffs() { var deg, dif, eps, fac, fct, inp = [], kno = [], pee = [], pol = [], quu = [], o = Decimal(0), x = o, index = document.fft.b.selectedIndex; var ell, emm = 0, inn, lli, lev, llr, lol, num, pfi, pfr, poi, por, shi, shr, str = "", uli, ulr, upl, vpf, vpo, vsh, f = 1, z; var fun = [ "log(z+1)", "atan(z)", "1/(1-z)", "2*i*exp(z)", "pi + e*z^3", "cos(2*z)", "abs(z)", "z/abs(z)", "abs(sin(z))", "sin(1/z)" ]; // check function and initialise variables if (index > 0) document.fft.pbsize.value = fun[index - 1]; fct = document.fft.pbsize.value.toString(); // Avoid alert on first load / empty input if (!fct || !fct.trim()) { return true; } eps = 1 / Math.pow(2, Number(document.fft.eps.value)); por = (isNaN(document.fft.pore.value)) ? 0 : Number(document.fft.pore.value); poi = (isNaN(document.fft.poim.value)) ? 0 : Number(document.fft.poim.value); ulr = (isNaN(document.fft.ulre.value)) ? 0 : Number(document.fft.ulre.value); uli = (isNaN(document.fft.ulim.value)) ? 0 : Number(document.fft.ulim.value); llr = (isNaN(document.fft.llre.value)) ? 0 : Number(document.fft.llre.value); lli = (isNaN(document.fft.llim.value)) ? 0 : Number(document.fft.llim.value); pfr = (isNaN(document.fft.pfre.value)) ? 0 : Number(document.fft.pfre.value); pfi = (isNaN(document.fft.pfim.value)) ? 0 : Number(document.fft.pfim.value); shr = (isNaN(document.fft.shre.value)) ? 0 : Number(document.fft.shre.value); shi = (isNaN(document.fft.shim.value)) ? 0 : Number(document.fft.shim.value); vpo = new Complex(Decimal(por), Decimal(poi)); upl = new Complex(Decimal(ulr), Decimal(uli)); lol = new Complex(Decimal(llr), Decimal(lli)); vpf = new Complex(Decimal(pfr), Decimal(pfi)); vsh = new Complex(Decimal(shr), Decimal(shi)); var F; try { F = compileUserFunction(fct, vpf, vsh); } catch (e) { return alert(e.message); } if (Number(document.fft.inn.value) > 0) inn = Number( document.fft.inn.value); if (Number(document.fft.com.value) > 0) com = Number( document.fft.com.value); if (Number(document.fft.opt.value) > 0) opt = Number( document.fft.opt.value); if (Number(document.fft.dif.value) >= 0) dif = Number( document.fft.dif.value); if (Number(document.fft.ite.value) >= 0 && dif == 0 && opt != 3) dif = -Number(document.fft.ite.value); else document.fft.ite.value = 0; if (Number(document.fft.exp.value) > 0) exp = Math.pow(2, Number(document.fft.exp.value)); switch (opt) { // compute (reverse) Taylor series case 1 : case 2 : initialise_tft(inp, exp * 2, F, vpo, opt, eps); if (inp[0] !== null) { compute_fft(inp); compute_taylor(inp, exp * 2, dif, eps); if (opt == 2) { z = inp[0].cneg(); compute_reverse(inp, exp * 2, vpo, eps); // Nullstelle der Umkehrung f = inp[exp * 2 - 1]; for (var l = exp * 2 - 2; l >= 0; l--) { f = f.cmul(z).cadd(inp[l]); } vpo = z.cneg(); } var taylorRootInfo = null; var taylorRootReport = ""; if (opt == 1 && document.fft.troot && document.fft.troot.checked) { taylorRootInfo = output_taylor_roots_from_coeffs(inp, dif, vpo, F); taylorRootReport = taylorRootInfo.text; } document.fft.result.value = output_taylor(inp, fct, dif, inn, upl, lol, f, F, taylorRootReport); quu[0] = f; // Taylor-Nullstellen für die Grafik anhängen if (taylorRootInfo && taylorRootInfo.rootsZ) { quu.taylorRoots = taylorRootInfo.rootsZ; } else { delete quu.taylorRoots; } pee = inp; ell = exp; } break; // compute Fourier series case 3 : exp /= 2; initialise_fft(inp, exp, F); if (inp[0] !== null) { compute_fft(inp); document.fft.result.value = compute_fourier(inp, exp * 2, 0); quu[0] = f; pee = inp; ell = exp; } exp /= 2; break; // compute zero of function case 4 : exp /= 2; lev = Number(document.fft.lev.value); // Für reine Iterationen (lev <= 1) genügt eine Auswertung; sonst Taylor-Daten via TFT/FFT initialise_tft(inp, exp, F, vpo, opt, eps); if (inp[0] !== null) { if (lev > 1) compute_fft(inp); quu[0] = compute_simpson(inp, exp, vpo, lev, F); pee = quu[0].split("|"); document.fft.result.value = pee[0]; quu[0] = Complex( Decimal(pee[1]), Decimal((pee.length > 2) ? pee[2] : "0") ); pee = inp; ell = exp; } exp *= 2; break; // Compute pole of function via root search applied to 1/F case 5 : exp /= 2; lev = Number(document.fft.lev.value); var G = makeReciprocalFunction(F); // Same machinery as for roots, but applied to G = 1/F. initialise_tft(inp, exp, G, vpo, 4, eps); if (inp[0] !== null) { if (lev > 1) compute_fft(inp); quu[0] = compute_simpson(inp, exp, vpo, lev, G); pee = quu[0].split("|"); var poleCandidate = Complex( Decimal(pee[1]), Decimal((pee.length > 2) ? pee[2] : "0") ); var out = ""; out += "Polsuche über die Nullstellenbestimmung von 1/F.\n"; out += "-------------------------------------------------\n\n"; out += pee[0] + "\n"; out += "Berechneter Polkandidat:\n"; out += "Re(z) = " + poleCandidate.re.toString() + "\n"; out += "Im(z) = " + poleCandidate.im.toString() + "\n\n"; out += verifyPoleCandidate(F, poleCandidate); document.fft.result.value = out; quu[0] = poleCandidate; pee = inp; ell = exp; } exp *= 2; break; // Berechne Padé-Approximation (Erweiterter Schutz gegen Singularitäten/NaN) case 6 : // Ermittle native komplexe Nullstellen mittels des neuen Toleranzschlauch-Scans var rootsF = findRootsInStrip(F, false, 2000); var rootsG = findRootsInStrip(F, true, 2000); var Nf = rootsF.length, Ng = rootsG.length; // Komplexe Deflations-Logik var F_deflated = function(z) { var tol = Decimal("1e-11"), ch = new Complex(Decimal("1e-7"), Decimal(0)), nearSingularity = false; for (var i = 0; i < rootsF.length; i++) if (z.csub(rootsF[i]).re.abs().add(z.csub(rootsF[i]).im.abs()).lt(tol)) nearSingularity = true; for (var i = 0; i < rootsG.length; i++) if (z.csub(rootsG[i]).re.abs().add(z.csub(rootsG[i]).im.abs()).lt(tol)) nearSingularity = true; var evalZ = nearSingularity ? z.cadd(ch) : z; var val = F(evalZ), prodF = ONE, prodG = ONE; // Direkte Anwendung der Complex-Objekte for (var i = 0; i < rootsF.length; i++) prodF = prodF.cmul(evalZ.csub(rootsF[i])); for (var i = 0; i < rootsG.length; i++) prodG = prodG.cmul(evalZ.csub(rootsG[i])); return val.cmul(prodG).cdiv(prodF); }; initialise_tft(inp, exp * 2, F_deflated, vpo, 1, eps); if (inp[0] !== null) { compute_fft(inp); compute_taylor(inp, exp * 2, 0, eps); var ui_ell = Number(document.fft.ell.value), ui_emm = Number(document.fft.emm.value); // Strukturelle Analyse var rest_ell_opt = 0, noise = Decimal("1e-10"); for(var k = 0; k < 30; k++) if (inp[k].re.abs().gt(noise) || inp[k].im.abs().gt(noise)) rest_ell_opt = k; var sug_L = (rest_ell_opt < 25) ? rest_ell_opt : 4; var sug_M = (rest_ell_opt < 25) ? 0 : 4; var opt_L = sug_L + Nf, opt_M = sug_M + Ng; var outStr = "VORGEZOGENE ABSPALTUNG VON LINEARFAKTOREN\n-----------------------------------------\n\n"; outStr += "1. ERKANNTE STRUKTUR (heuristische Analyse)\n"; outStr += "Vorgeschlagene Mindestgrade: Zählergrad " + opt_L + " und Nennergrad " + opt_M + "\n\n"; var rest_L = ui_ell - Nf, rest_M = ui_emm - Ng; if (rest_L < 0 || rest_M < 0) { outStr += "2. FEHLER: Die ausgewählten Grade sind für die gefundenen Nullstellen/Pole zu klein.\n"; document.fft.result.value = outStr; } else if (rest_L + rest_M > 31) { outStr += "2. FEHLER: Die ausgewählten Padé-Grade erfordern zu viele Taylorkoeffizienten.\n"; outStr += "Für die aktuelle Taylor-Ordnung müssen die abgespaltenen Grade folgende Bedingung erfüllen: "; outStr += "Restzählergrad + Restnennergrad <= 31.\n"; outStr += "Aktuell: " + rest_L + " + " + rest_M + " = " + (rest_L + rest_M) + ".\n"; document.fft.result.value = outStr; } else { var sel_p = [], sel_q = []; for(var i=0; i<64; i++) { sel_p[i] = ZER; sel_q[i] = ZER; } var pade_msg = output_pade(inp, 0, rest_L, rest_M, sel_p, sel_q); var hasNaN = false; for(var i=0; i<=rest_L; i++) if(sel_p[i].re.isNaN()) hasNaN = true; for(var i=0; i<=rest_M; i++) if(sel_q[i].re.isNaN()) hasNaN = true; var isSolvable = (pade_msg.indexOf("nicht lösbar") === -1) && !hasNaN; if (!isSolvable) { for(var i=0; i<64; i++) { sel_p[i] = (i <= rest_L) ? (inp[i] || ZER) : ZER; sel_q[i] = (i === 0) ? ONE : ZER; } } var reconstruct = function(p_coeffs, q_coeffs, rF, rG, centre) { var resP = []; for (var i = 0; i < 64; i++) { resP[i] = (p_coeffs[i]) ? p_coeffs[i] : ZER; } // Multipliziere Zähler mit den lokalen Faktoren (w - r_loc), // wobei w = z - centre und r_loc = r - centre sind. for (var i = 0; i < rF.length; i++) { var rloc = rF[i].csub(centre); var temp = []; for (var k = 0; k < 64; k++) { temp[k] = ((k > 0) ? resP[k - 1] : ZER).csub(resP[k].cmul(rloc)); } resP = temp; } var resQ = []; for (var i = 0; i < 64; i++) { resQ[i] = (q_coeffs[i]) ? q_coeffs[i] : ZER; } // Multipliziere Nenner mit den lokalen Faktoren (w - p_loc). for (var i = 0; i < rG.length; i++) { var ploc = rG[i].csub(centre); var temp = []; for (var k = 0; k < 64; k++) { temp[k] = ((k > 0) ? resQ[k - 1] : ZER).csub(resQ[k].cmul(ploc)); } resQ = temp; } return { p: resP, q: resQ }; }; var finalSel = reconstruct(sel_p, sel_q, rootsF, rootsG, vpo); outStr += "2. KOEFFIZIENTEN DER GEWÄHLTEN APPROXIMATION\n"; if (!isSolvable) outStr += "Hinweis: System ist singulär (Überanpassung). Verwende Taylor-Fallback.\n"; outStr += "Auf sechzehn Dezimalstellen gerundete Koeffizienten (Grad " + ui_ell + "/" + ui_emm + "):\n"; var formatHighPrec = function(val, label, k) { if (!val || val.re.isNaN()) val = ZER; var s, res = (k < 10) ? label + "[0" + k + "] = " : label + "[" + k + "] = "; s = ((val.re.gte(o)) ? "+" : "") + val.re.toExponential(50, 4).replace("e", ")e"); s = s.padEnd(59); res += s.substring(0, 17) + "(" + s.substring(17); s = ((val.im.gte(o)) ? " +" : " ") + val.im.toExponential(50, 4).replace("e", ")e"); res += s.substring(0, 17) + "(" + s.substring(17) + "im\n"; return res; }; for (var k = 0; k <= ui_ell; k++) outStr += formatHighPrec(finalSel.p[k], "p", k); for (var k = 0; k <= ui_emm; k++) outStr += formatHighPrec(finalSel.q[k], "q", k); document.fft.result.value = outStr; ell = ui_ell; emm = ui_emm; for(var i = 0; i < 64; i++) { pee[i] = finalSel.p[i] || ZER; quu[i] = finalSel.q[i] || ZER; } } } break; // compute spline approximation case 7 : initialise_tft(inp, exp * 2, F, vpo, 1, eps); if (inp[0] !== null) { deg = Number(document.fft.deg.value); num = Number(document.fft.num.value); compute_fft(inp); compute_taylor(inp, exp * 2, 0, eps); kno[0] = new Complex(LZ.neg(), o); lev = new Complex(LZ.mul(Decimal(2).div(Decimal(num - 1))), o); for (var i = 0; i < num; i++) { pol[i] = []; initialise_tft(pol[i], exp * 2, F, kno[i], 1, eps); compute_fft(pol[i]); compute_taylor(pol[i], exp * 2, 0, eps); kno[i+1] = kno[i].cadd(lev); } document.fft.result.value = output_taylor(inp, fct, 0, inn, upl, lol, 1, F) + output_spline(pol, kno, deg, num); quu[0] = f; pee = pol; ell = num; emm = deg; } break; default : } if (inp[0] === null) document.fft.result.value = str + "Auswertung ist nicht möglich."; if (fct > "") draw_graph(exp, F, com, opt, ell, emm, pee, quu, vpo, dif, kno, pol); return true; } function draw_graph(asize, F, compl, optio, eldeg, emdeg, numer, denom, value, diffe, knots, polys) { function drawRootCrosses(rootList, colour) { if (!rootList || !rootList.length) return; for (var i = 0; i < rootList.length; i++) { var mark = rootList[i]; if (!mark) continue; // Wie bei der einfachen Nullstellenbestimmung: // Nur nahe reelle Nullstellen auf dieser reellen Grafik markieren. if (mark.im.abs().gt(Decimal("1e-8"))) continue; var xpix = CENTRE + Math.round(CENTRE * mark.re.toNumber() / Math.PI); if (xpix < 0 || xpix > twice) continue; canvas.text(xpix, CENTRE, "X").attr({fill: colour}); } } var bnd = 1e5, nul = Decimal(0), fac = new Complex(Decimal(-diffe), nul), fra = new Complex(LZ.div(Decimal(asize)), nul), graph = [], inp, padeg = []; var max = -bnd, min = bnd, inc, len, meths = [" ", "Taylor", "Umkehrung", "Fourier", "Nullstelle", "Pol ", "Padé ", "Spline"], k = -twice, m = 2 * asize; var den, hlp, tst, r = asize - 0.5, quo = 2 * r / twice, z = Complex(LZ, nul).cneg(), end_x, end_y, horizontal_axis, vertical_axis, start_x, start_y; switch (optio) { // compute (reverse) Taylor series case 1: case 2: z = z.csub(value); inc = Math.max(diffe, 0); for (var n = 0; n < 2 * asize; n++) { hlp = numer[eldeg]; for (var l = eldeg + inc - 1; l >= inc; l--) hlp = hlp.cmul(z).cadd(numer[l]); padeg[n] = (diffe < 0) ? hlp.cmul(z.cpow(fac)) : hlp; z = z.cadd(fra); } break; // compute Fourier series case 3: for (var n = 0; n < 2 * asize; n++) { hlp = numer[0]; for (var l = 1 - m; l < m; l++) hlp = hlp.cadd(z.cmul(Complex(nul, Decimal(-l))).cexp().cmul(numer[m - l])); padeg[n] = hlp; z = z.cadd(fra); } break; // compute Padé approximant case 6: var z_pade = z.csub(value); // Lokale Kopie von z für den Padé-Scan for (var n = 0; n < 2 * asize; n++) { var hlp = numer[eldeg] || ZER; for (var l = eldeg - 1; l >= 0; l--) hlp = hlp.cmul(z_pade).cadd(numer[l] || ZER); var den = denom[emdeg] || ONE; // Variable 'm_loop' statt 'm' verwenden, um Kollision zu vermeiden for (var m_loop = emdeg - 1; m_loop >= 0; m_loop--) den = den.cmul(z_pade).cadd(denom[m_loop] || ZER); if (den.cabs().re.lt(Decimal("1e-25"))) den = ONE; // Schutz gegen Division durch 0 padeg[n] = hlp.cdiv(den); z_pade = z_pade.cadd(fra); } break; // compute spline approximation case 7: fac = new Complex(LZ.mul(Decimal(2).div(Decimal(eldeg - 1))), nul); eldeg = 0; for (var n = 0; n < 2 * asize; n++) { hlp = polys[eldeg][emdeg]; z = z.csub(knots[eldeg]); // FIX: m_loop statt m nutzen for (var m_loop = emdeg - 1; m_loop >= 0; m_loop--) hlp = hlp.cmul(z).cadd(polys[eldeg][m_loop]); z = z.cadd(knots[eldeg]); padeg[n] = hlp.cmul(knots[++eldeg].csub(z)); hlp = polys[eldeg][emdeg]; z = z.csub(knots[eldeg]); for (var m_loop = emdeg - 1; m_loop >= 0; m_loop--) hlp = hlp.cmul(z).cadd(polys[eldeg][m_loop]); z = z.cadd(knots[eldeg]); padeg[n] = padeg[n].cadd(hlp.cmul(z.csub(knots[--eldeg]))).cdiv(fac); z = z.cadd(fra); if (z.re.toNumber() > knots[eldeg+1].re.toNumber()) eldeg++; } break; default : } // determine minimum and maximum z = Complex(LZ.neg(), nul); for (var n = 0; n < 2 * asize; n++) { graph[n] = F(z); tst = (compl == 1) ? graph[n].re.toNumber() : graph[n].im.toNumber(); if (tst > max) max = tst; if (tst < min) min = tst; z = z.cadd(fra); } if (max > bnd) max = bnd; if (min < -bnd) min = -bnd; bnd = Math.round(Math.max(Math.abs(max), Math.abs(min)) + 0.499); if (bnd < 1 || !isFinite(bnd)) bnd = 1; len = bnd.toString().length; // draw axes canvas.clear(); start_x = start_y = 0; end_x = end_y = twice; horizontal_axis = canvas.path("M" + start_x + " " + CENTRE + "L" + end_x + " " + CENTRE); horizontal_axis.attr({"stroke": COLOUR, "stroke-width": 1 }); vertical_axis = canvas.path("M" + CENTRE + " " + start_y + "L" + CENTRE + " " + end_y); vertical_axis.attr({ "stroke": COLOUR, "stroke-width": 1 }); // add texts canvas.text(20, 6, ((compl == 1) ? "Re" : "Im") + "(f(z))").attr({fill: COLOUR}); canvas.text(6, CENTRE + 6, "-π").attr({fill: COLOUR}); canvas.text(twice - 6, CENTRE + 6, "π").attr({fill: COLOUR}); canvas.text(CENTRE - 4 * len++, 6, bnd.toString()).attr({fill: COLOUR}); canvas.text(CENTRE - 4 * len, twice - 6, "-" + bnd.toString()).attr({fill: COLOUR}); var legendPrefix = (optio == 4 || optio == 5) ? "X " : "--- "; canvas.text( meths[optio].length * 3 + 5, 20, legendPrefix + meths[optio] ).attr({fill: "#FF0000"}); // draw curve of function inc = quo * k / 2 + r; bnd = CENTRE / bnd; while (++k < twice) { if (k % 2 == 0) { start_y = (compl == 1) ? CENTRE - Math.round(bnd * graph[Math.round(inc)].re.toNumber()) : CENTRE - Math.round(bnd * graph[Math.round(inc)].im.toNumber()); inc += quo; end_y = (compl == 1) ? CENTRE - Math.round(bnd * graph[Math.round(inc)].re.toNumber()) : CENTRE - Math.round(bnd * graph[Math.round(inc)].im.toNumber()); horizontal_axis = canvas.path("M" + (start_x - 1) + " " + start_y + "L" + ++start_x + " " + end_y); horizontal_axis.attr({"stroke": COLOUR, "stroke-width": 1 }); } } // Draw function root in green, then second result in red if available if ((optio == 4 || optio == 5) && denom && denom[0]) { var mark = denom[0]; // Only mark it on this real-axis graph if the imaginary part is tiny. if (mark.im.abs().lt(Decimal("1e-8"))) { var markColour = (optio == 5) ? "#FFFF00" : "#00FF00"; canvas.text( CENTRE + Math.round(CENTRE * mark.re.toNumber() / Math.PI), CENTRE, "X" ).attr({fill: markColour}); } } // optio == 0 means: draw only the original function. // optio == 4 means: root-finding. // optio == 5 means: pole-finding via 1/F. // In both cases no red approximation curve is required. if (optio == 0 || optio == 4 || optio == 5) return; // RESET FÜR ROTE KURVE start_x = 0; k = -twice; inc = quo * k / 2 + r; while (++k < twice) { if (k % 2 == 0) { var val_pad = padeg[Math.round(inc)]; if (val_pad && !val_pad.re.isNaN()) { start_y = (compl == 1) ? CENTRE - Math.round(bnd * val_pad.re.toNumber()) : CENTRE - Math.round(bnd * val_pad.im.toNumber()); inc += quo; var next_pad = padeg[Math.round(inc)] || val_pad; end_y = (compl == 1) ? CENTRE - Math.round(bnd * next_pad.re.toNumber()) : CENTRE - Math.round(bnd * next_pad.im.toNumber()); var red_path = canvas.path("M" + (start_x - 1) + " " + start_y + "L" + ++start_x + " " + end_y); red_path.attr({"stroke": "#FF0000", "stroke-width": 1.5 }); // Dickere rote Linie zur besseren Sichtbarkeit } else { inc += quo; start_x++; } } } if (optio == 1 && denom && denom.taylorRoots) { drawRootCrosses(denom.taylorRoots, "#00FF00"); } } // main programme build_form(); document.fft.result.value = "Kurze Bedienungsanleitung\n" + "=========================\n\n" + "1. Funktion auswählen oder eingeben\n" + "Wählen Sie links oben unter „Beispiele“ eine Beispielfunktion aus oder geben Sie\n" + "eine eigene Formel in der Variablen z ein, zum Beispiel:\n" + " log(z+1)\n" + " atan(z)\n" + " 1/(1-z)\n" + " 2*i*exp(z)\n" + " pi + e*z^3\n" + " sin(1/z)\n\n" + "Erlaubte Konstanten:\n" + " pi, e, i\n\n" + "Erlaubte Rechenzeichen:\n" + " +, -, *, /, ^ oder **\n" + "Auch implizite Multiplikation ist möglich, zum Beispiel 2z oder 2(z+1).\n\n" + "Erlaubte Funktionen:\n" + " exp, log, ln, sqrt, abs,\n" + " sin, cos, tan, cot, sec, csc,\n" + " asin, acos, atan, acot, asec, acsc,\n" + " sinh, cosh, tanh, coth, sech, csch,\n" + " asinh, acosh, atanh, acoth, asech, acsch.\n\n" + "2. Realteil oder Imaginärteil\n" + "Mit „Re f“ wird der Realteil der Funktion gezeichnet und ausgewertet.\n" + "Mit „Im f“ wird der Imaginärteil der Funktion gezeichnet und ausgewertet.\n\n" + "3. Verfahren\n" + " Taylorreihe berechnet Taylor-Koeffizienten am Punkt; mit Checkbox auch Nullstellen des Taylorpolynoms.\n" + " Umkehrung berechnet die Umkehrreihe einer Taylorreihe.\n" + " Fourierreihe berechnet Fourier-Koeffizienten auf [-pi, pi].\n" + " Nullstelle sucht eine Nullstelle der Funktion.\n" + " Pol sucht einen Pol über die Nullstellensuche von 1/F.\n" + " Padé-Approximation berechnet eine rationale Padé-Approximation.\n" + " Spline-Approximation berechnet lokale Taylor-/Spline-Approximationen.\n\n" + "4. Parameter\n" + " Grad Grad der lokalen Spline-Polynome.\n" + " Stufe Iterations- bzw. Konvergenzstufe bei Nullstelle und Pol.\n" + " -lb(Epsilon) bestimmt Epsilon = 2^(-Wert).\n" + " Ableitung Ordnung der Ableitung.\n" + " Integral Ordnung der Integration, falls keine Ableitung gewählt ist.\n" + " Exponent steuert die Auflösung der Grafik.\n" + " Knoten Anzahl der Knoten für die Spline-Approximation.\n" + " Anzahl Anzahl der Teilintervalle für Summen/Integrale.\n" + " Zählergrad Grad des Padé-Zählers.\n" + " Nennergrad Grad des Padé-Nenners.\n\n" + "5. Komplexe Eingabewerte\n" + " Punkt Entwicklungs- bzw. Startpunkt.\n" + " Obergrenze obere Grenze für Auswertungen, Summen oder Integrale.\n" + " Untergrenze untere Grenze für Auswertungen, Summen oder Integrale.\n" + " Vorfaktor Vorfaktor der affinen Einbettung.\n" + " Verschiebung Verschiebung der affinen Einbettung.\n\n" + "Alle diese Werte besitzen jeweils einen Realteil und einen Imaginärteil.\n" + "Intern wird die Funktion als f(Vorfaktor*z - Verschiebung) ausgewertet.\n\n" + "6. Grafik\n" + "Die weiße Kurve zeigt die eingegebene Funktion.\n" + "Die rote Kurve zeigt die berechnete Approximation, also Taylorreihe,\n" + "Umkehrung, Fourierreihe, Padé-Approximation oder Spline-Approximation.\n" + "Bei Nullstelle und Pol wird keine rote Approximation gezeichnet; stattdessen wird der\n" + "gefundene Punkt grün bzw. gelb markiert, sofern er nahe genug zur reellen Achse liegt.\n\n" + "7. Hinweise\n" + "Bei Singularitäten, Verzweigungspunkten oder sehr nahe an Polen können\n" + "Taylorreihe und Padé-Approximation empfindlich reagieren.\n" + "Für Funktionen wie sin(1/z) ist oft ein Punkt abseits von z = 0 sinnvoll,\n" + "zum Beispiel Punkt: Realteil = 1, Imaginärteil = 0.\n" + "Die Spline-Approximation ist bei stark oszillierenden Funktionen oft robuster\n" + "als eine einzelne globale Padé-Approximation.\n\n" + "Nach Eingabe oder Änderung eines Parameters bitte „Berechnen“ drücken."; compute_coeffs();