Skip to content

Commit b88b1c1

Browse files
committed
Add new functions
1 parent 15cb6cf commit b88b1c1

File tree

7 files changed

+227
-19
lines changed

7 files changed

+227
-19
lines changed

src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export type AppState = {
2727
};
2828

2929
const DEFAULT_OPTIONS = {
30-
angleUnit: "deg",
30+
angleUnit: "rad",
3131
resultAccuracy: 8,
3232
language: getDefaultLanguage(),
3333
preserveSessions: true,

src/math/documentation.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { Language } from "@/lang";
2+
13
type Function = {
24
description: Translatable;
35
usage: string | Translatable;
@@ -8,9 +10,8 @@ type ResolvedFunction = {
810
usage: string;
911
};
1012

11-
type Translatable = {
13+
type Translatable = Partial<Record<Language, string>> & {
1214
fi: string;
13-
sv: string;
1415
en: string;
1516
};
1617

@@ -117,14 +118,17 @@ const documentation: Record<string, Function> = {
117118

118119
export function getDocumentation(
119120
fn: string,
120-
lang = "fi" as "fi" | "en" | "sv",
121+
lang = "fi" as Language,
121122
): ResolvedFunction | null {
122123
const doc = documentation[fn];
123124
if (!doc) return null;
124-
const usage = typeof doc.usage === "object" ? doc.usage[lang] : doc.usage;
125+
const usage =
126+
typeof doc.usage === "object"
127+
? (doc.usage[lang] ?? doc.usage.en)
128+
: doc.usage;
125129

126130
return {
127-
description: doc.description[lang],
128-
usage,
131+
description: doc.description[lang] ?? doc.description.en,
132+
usage: usage,
129133
};
130134
}

src/math/internal/evaluator.ts

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export type UserVariable = {
5757

5858
export type UserObject = UserVariable | UserFunction;
5959

60-
const RESERVED_VARIABLES = [
60+
export const RESERVED_VARIABLES = [
6161
"pi",
6262
"e",
6363
"ans",
@@ -89,7 +89,7 @@ const RESERVED_VARIABLES = [
8989
"nthroot",
9090
"arccos",
9191
"arctan",
92-
"arcos",
92+
"arcsin",
9393
"lg",
9494
"degrees",
9595
"radians",
@@ -102,6 +102,20 @@ const RESERVED_VARIABLES = [
102102
"ℇ",
103103
"𝑒",
104104
"ℯ",
105+
"abs",
106+
"cosh",
107+
"sinh",
108+
"tanh",
109+
"sum",
110+
"variance",
111+
"min",
112+
"max",
113+
"round",
114+
"int",
115+
"lngamma",
116+
"frac",
117+
"sgn",
118+
"median",
105119
];
106120
const SYNTAX_ERRORS = [
107121
"UNEXPECTED_EOF",
@@ -254,7 +268,10 @@ export default function evaluate(
254268
),
255269
)
256270
.with(
257-
{ type: "func", name: P.union("lg", "degrees", "radians") },
271+
{
272+
type: "func",
273+
name: P.union("lg", "degrees", "radians", "frac", "sgn"),
274+
},
258275
(token) => {
259276
// Custom methods with one argument
260277
const funcName = token.name;
@@ -285,17 +302,23 @@ export default function evaluate(
285302
.otherwise(() => err({ type: "INVALID_ARG_COUNT" } as const));
286303
},
287304
)
288-
.with({ type: "func", name: P.union("average") }, (token) => {
289-
// Custom methods with unlimited params
290-
const funcName = token.name;
291-
const func = functions[funcName];
305+
.with(
306+
{
307+
type: "func",
308+
name: P.union("average", "sum", "variance", "min", "max", "median"),
309+
},
310+
(token) => {
311+
// Custom methods with unlimited params
312+
const funcName = token.name;
313+
const func = functions[funcName];
292314

293-
const res = parseFunction();
294-
if (res.isErr()) return err(res.error);
295-
const args = res.value;
315+
const res = parseFunction();
316+
if (res.isErr()) return err(res.error);
317+
const args = res.value;
296318

297-
return ok(func(...args));
298-
})
319+
return ok(func(...args));
320+
},
321+
)
299322
.with(
300323
{
301324
type: "func",
@@ -321,6 +344,12 @@ export default function evaluate(
321344
"csc",
322345
"cot",
323346
"cbrt",
347+
"abs",
348+
"sinh",
349+
"tanh",
350+
"cosh",
351+
"round",
352+
"lngamma",
324353
),
325354
},
326355
(token) => {

src/math/internal/functions.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,60 @@ function degrees(rad: LargeNumber): LargeNumber {
7272
return rad.mul(LargeNumber.RAD_DEG_RATIO).run();
7373
}
7474

75+
function sum(...nums: LargeNumber[]) {
76+
return nums.reduce((a, b) => a.add(b), new LargeNumber(0).op()).run();
77+
}
78+
79+
function variance(...nums: LargeNumber[]) {
80+
if (nums.length === 0) return new LargeNumber(0);
81+
const mean = average(...nums);
82+
83+
return nums
84+
.reduce((acc, num) => {
85+
return acc.add(num.sub(mean).pow(new LargeNumber(2)));
86+
}, new LargeNumber(0).op())
87+
.div(new LargeNumber(nums.length))
88+
.run();
89+
}
90+
91+
function min(...nums: LargeNumber[]) {
92+
return nums.reduce((a, b) => (a.gt(b) ? b : a), new LargeNumber(0));
93+
}
94+
95+
function max(...nums: LargeNumber[]) {
96+
return nums.reduce((a, b) => (a.gt(b) ? a : b), new LargeNumber(0));
97+
}
98+
99+
function frac(num: LargeNumber) {
100+
return num.sub(num.floor()).run();
101+
}
102+
103+
function sgn(num: LargeNumber) {
104+
return num.gt(new LargeNumber(0))
105+
? new LargeNumber(1)
106+
: num.lt(new LargeNumber(0))
107+
? new LargeNumber(-1)
108+
: new LargeNumber(0);
109+
}
110+
111+
function median(...nums: LargeNumber[]) {
112+
if (nums.length === 0) return new LargeNumber(0);
113+
if (nums.length === 1) return nums[0];
114+
115+
const sorted = nums.sort((a, b) => (a.gt(b) ? 1 : -1));
116+
117+
if (nums.length % 2 === 0) {
118+
return average(
119+
...sorted.slice(
120+
Math.round(nums.length / 2) - 1,
121+
Math.round(nums.length / 2) + 1,
122+
),
123+
);
124+
}
125+
126+
return nums[Math.floor(nums.length / 2)];
127+
}
128+
75129
export const functions = {
76130
log,
77131
lg,
@@ -81,4 +135,11 @@ export const functions = {
81135
radians,
82136
degrees,
83137
nthroot,
138+
sum,
139+
variance,
140+
min,
141+
max,
142+
frac,
143+
sgn,
144+
median,
84145
} as const;

src/math/internal/tokenizer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ const tokenMatchers = [
100100
.with("log10", () => "log" as const)
101101
.with("√", () => "sqrt" as const)
102102
.with("arcsin", () => "asin" as const)
103+
.with("int", () => "floor" as const)
103104
.with("arccos", () => "acos" as const)
104105
.with("arctan", () => "atan" as const)
105106
.with("arsinh", () => "asinh" as const)

src/styles/app.scss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,14 @@
7979
}
8080
}
8181

82+
@media screen and (display-mode: standalone) {
83+
body.limit-size #app {
84+
max-width: 100vw;
85+
max-height: 100vh;
86+
border: none;
87+
}
88+
}
89+
8290
@media screen and (max-width: 719px) {
8391
.fullscreen-option {
8492
display: none;

src/test/speedcrunch.test.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { expect, test } from "bun:test";
2+
import { RESERVED_VARIABLES } from "@/math/internal/evaluator";
3+
4+
// Here's a list of all SpeedCrunch functions
5+
const speedCrunchFunctions = [
6+
"abs",
7+
"absdev",
8+
// "and", // Binary/bitwise not planned
9+
"arccos",
10+
"arcosh",
11+
"arcsin",
12+
"arctan",
13+
"arctan2",
14+
"arsinh",
15+
"artanh",
16+
"average",
17+
// "bin", // Binary not supported
18+
"binomcdf",
19+
"binommean",
20+
"binompmf",
21+
"binomvar",
22+
// "cart", // Complex numbers not supported
23+
"cbrt",
24+
"ceil",
25+
"cos",
26+
"cosh",
27+
"cot",
28+
"csc",
29+
// "dec", // Non dec not supported
30+
"degrees",
31+
"erf",
32+
"erfc",
33+
"exp",
34+
"floor",
35+
"frac",
36+
"gamma",
37+
"gcd",
38+
"geomean",
39+
// "hex", // Hexadecimals not supported
40+
"hypercdf",
41+
"hypermean",
42+
"hyperpmf",
43+
"hypervar",
44+
"idiv",
45+
/*"ieee754_decode", // These are highly tech stuff, no one is going to use these realistically in an exam
46+
"ieee754_double_decode",
47+
"ieee754_double_encode",
48+
"ieee754_encode",
49+
"ieee754_half_decode",
50+
"ieee754_half_encode",
51+
"ieee754_quad_decode",
52+
"ieee754_quad_encode",
53+
"ieee754_single_decode",
54+
"ieee754_single_encode",*/
55+
// "imag", // Complex numbers not supported
56+
"int",
57+
"lb",
58+
"lg",
59+
"ln",
60+
"lngamma",
61+
"log",
62+
// "mask", // Binary/bitwise not planned
63+
"max",
64+
"median",
65+
"min",
66+
"mod",
67+
"ncr",
68+
// "not", // Binary/bitwise not planned
69+
"npr",
70+
// "oct", // Octal not supported
71+
// "or", // Binary/bitwise not planned
72+
// "phase", // Complex numbers not supported
73+
"poicdf",
74+
"poimean",
75+
"poipmf",
76+
"poivar",
77+
//"polar", // Complex numbers not supported
78+
"product",
79+
"radians",
80+
// "real", // Complex numbers not supported
81+
"round",
82+
"sec",
83+
"sgn",
84+
// "shl", // Bitwise/binary not planned
85+
// "shr",
86+
"sin",
87+
"sinh",
88+
"sqrt",
89+
"stddev",
90+
"sum",
91+
"tan",
92+
"tanh",
93+
"trunc",
94+
// "unmask", // Binary/bitwise not planned
95+
"variance",
96+
// "xor", // Binary/bitwise not planned
97+
];
98+
99+
for (const func of speedCrunchFunctions) {
100+
test(`${func}()`, () => {
101+
const find = RESERVED_VARIABLES.find((i) => i === func);
102+
// Doing this so the output isn't filled with the entire array
103+
expect(find).not.toBeUndefined();
104+
});
105+
}

0 commit comments

Comments
 (0)