diff --git a/index.html b/index.html index ee387c6..6119100 100644 --- a/index.html +++ b/index.html @@ -23,6 +23,7 @@
+ diff --git a/main.js b/main.js index 33b5ad1..4f22f6b 100644 --- a/main.js +++ b/main.js @@ -130,6 +130,7 @@ document.querySelector("#rparen").addEventListener("click", () => keyPress(")")) document.querySelector("#pow").addEventListener("click", () => keyPress("^")); document.querySelector("#inv").addEventListener("click", () => keyPress("^-1")); document.querySelector("#sqrt").addEventListener("click", () => keyPress("sqrt(")); +document.querySelector("#ord").addEventListener("click", () => keyPress("ord(")); document.querySelector("#clear").addEventListener("click", clear); document.querySelector("#enter").addEventListener("click", evaluate); diff --git a/modules/compute.js b/modules/compute.js index 7e953ee..d038194 100644 --- a/modules/compute.js +++ b/modules/compute.js @@ -1,4 +1,4 @@ -import { modinv, modpow, modsqrt } from "./math.js"; +import { modinv, modpow, modsqrt, ord } from "./math.js"; function binaryOpPop(stack) { const b = stack.pop(); @@ -56,6 +56,10 @@ function compute(queue, modulus) { const a = stack.pop(); const s = modsqrt(a, modulus); stack.push(s); + } else if (token === "ord") { + const a = stack.pop(); + const r = ord(a, modulus); + stack.push(r); } } diff --git a/modules/lexer.js b/modules/lexer.js index b83bbdd..45b383e 100644 --- a/modules/lexer.js +++ b/modules/lexer.js @@ -1,7 +1,11 @@ function tokenize(expr) { // NOTE: not handling whitespace // NOTE: currently ends early if string doesn't match token - const regexp = /[0-9]+|[-+*/^]|\(|\)|sqrt/gy; + + // TODO: it would probably be beneficial to create some sort of + // function token instead of updating strings in several different + // places + const regexp = /[0-9]+|[-+*/^]|\(|\)|sqrt|ord/gy; const matches = expr.matchAll(regexp); const tokens = []; @@ -10,12 +14,14 @@ function tokenize(expr) { tokens.push(BigInt(match[0])); } else if (/[-+*^/]/.test(match[0])) { tokens.push(match[0]) - } else if (match[0] == "(") { + } else if (match[0] === "(") { tokens.push("("); - } else if (match[0] == ")") { + } else if (match[0] === ")") { tokens.push(")"); - } else if (match[0] == "sqrt") { + } else if (match[0] === "sqrt") { tokens.push("sqrt") + } else if (match[0] === "ord") { + tokens.push("ord") } } diff --git a/modules/math.js b/modules/math.js index 4add6a1..150f5fd 100644 --- a/modules/math.js +++ b/modules/math.js @@ -181,4 +181,28 @@ function modsqrt(n, modulus) { return tonelliShanks(n, modulus); } -export { modsqrt, modinv, modpow }; +function ord(n, modulus) { + n %= modulus; + if (n < 0n) { + n += modulus; + } + + const [g, s, t] = xgcd(n, modulus); + if (g !== 1n) { + throw new Error(`can't compute multiplicative order - ${n} and ${modulus} are not coprime`); + } + + // NOTE: this is a hard problem, but there are more efficient approaches + + let k = 1n; + let a = n; + while (a !== 1n) { + a *= n; + a %= modulus; + k += 1n; + } + + return k; +} + +export { modsqrt, modinv, modpow, ord }; diff --git a/modules/parser.js b/modules/parser.js index 7d7bc77..4c93a56 100644 --- a/modules/parser.js +++ b/modules/parser.js @@ -27,6 +27,8 @@ function popOps(opstack, queue, op, powInStack) { // rather than complicate things by evaluating exponent expressions // normally instead of modularly (and consequently needing to deal with // fractions) we simply require exponents to be integers + // NOTE: as written this allows for the exponent to be a function, not + // sure if that should change or not if (powInStack && op !== "u") { throw new Error("exponent must be an integer, not an expression"); } @@ -48,7 +50,8 @@ function popBetweenParens(opstack, queue, powInStack) { } opstack.pop(); - if (opstack.at(-1) === "sqrt") { + const t = opstack.at(-1); + if (t === "sqrt" || t === "ord") { const func = opstack.pop(); queue.push(func); } @@ -95,6 +98,8 @@ function shunt(tokens) { powInStack = popBetweenParens(opstack, queue, powInStack); } else if (token === "sqrt") { opstack.push(token); + } else if (token === "ord") { + opstack.push(token); } lastToken = token;