add multiplicative order button

This commit is contained in:
filifa 2025-12-11 23:49:34 -05:00
parent 39c009beb4
commit 4e91b1dc14
6 changed files with 48 additions and 7 deletions

View File

@ -23,6 +23,7 @@
<output id="result" for="expr modulus"></output>
<div id="extra-buttons">
<button id="ord" type="button">ord</button>
<button id="sqrt" type="button"></button>
<button id="inv" type="button">x<sup>-1</sup></button>
<button id="pow" type="button">^</button>

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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")
}
}

View File

@ -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 };

View File

@ -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;