From 11aaf86a19867b1ed1aa8fa96a00a3b020787416 Mon Sep 17 00:00:00 2001 From: s-kybound Date: Sun, 14 Apr 2024 22:13:09 +0800 Subject: [PATCH 1/4] implement rounding functions and complex number functions --- src/stdlib/base.ts | 10 +++ src/stdlib/core-math.ts | 170 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 180 insertions(+) diff --git a/src/stdlib/base.ts b/src/stdlib/base.ts index 19fd3f6..156f973 100644 --- a/src/stdlib/base.ts +++ b/src/stdlib/base.ts @@ -419,6 +419,16 @@ export const tan: Function = core.tan; export const asin: Function = core.asin; export const acos: Function = core.acos; export const atan: Function = core.atan; +export const floor: Function = core.floor; +export const ceiling: Function = core.ceiling; +export const truncate: Function = core.truncate; +export const round: Function = core.round; +export const make$45$rectangular: Function = core.make$45$rectangular; +export const make$45$polar: Function = core.make$45$polar; +export const real$45$part: Function = core.real$45$part; +export const imag$45$part: Function = core.imag$45$part; +export const magnitude: Function = core.magnitude; +export const angle: Function = core.angle; export const PI = core.PI; export const E = core.E; diff --git a/src/stdlib/core-math.ts b/src/stdlib/core-math.ts index 6918d55..c954f2b 100644 --- a/src/stdlib/core-math.ts +++ b/src/stdlib/core-math.ts @@ -1315,3 +1315,173 @@ export const atan = (n: SchemeNumber, m?: SchemeNumber): SchemeNumber => { } return SchemeReal.build(Math.atan(n.coerce())); }; + +export const floor = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("floor: expected number"); + } + if (!is_real(n)) { + // complex number case + throw new Error("floor: expected real number"); + } + if (n.numberType === NumberType.INTEGER) { + return n; + } + if (n.numberType === NumberType.RATIONAL) { + // floor is numerator // denominator + const rational = n as SchemeRational; + const numerator = rational.getNumerator(); + const denominator = rational.getDenominator(); + return SchemeInteger.build(numerator / denominator); + } + return SchemeReal.build(Math.floor(n.coerce())); +}; + +export const ceiling = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("ceiling: expected number"); + } + if (!is_real(n)) { + // complex number case + throw new Error("ceiling: expected real number"); + } + if (n.numberType === NumberType.INTEGER) { + return n; + } + if (n.numberType === NumberType.RATIONAL) { + // ceiling is (numerator + denominator - 1) // denominator + const rational = n as SchemeRational; + const numerator = rational.getNumerator(); + const denominator = rational.getDenominator(); + return SchemeInteger.build((numerator + denominator - 1n) / denominator); + } + return SchemeReal.build(Math.ceil(n.coerce())); +}; + +export const truncate = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("truncate: expected number"); + } + if (!is_real(n)) { + // complex number case + throw new Error("truncate: expected real number"); + } + if (n.numberType === NumberType.INTEGER) { + return n; + } + if (n.numberType === NumberType.RATIONAL) { + // truncate is also just numerator // denominator + // exactly like floor + const rational = n as SchemeRational; + const numerator = rational.getNumerator(); + const denominator = rational.getDenominator(); + return SchemeInteger.build(numerator / denominator); + } + return SchemeReal.build(Math.trunc(n.coerce())); +}; + +export const round = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("round: expected number"); + } + if (!is_real(n)) { + // complex number case + throw new Error("round: expected real number"); + } + if (n.numberType === NumberType.INTEGER) { + return n; + } + if (n.numberType === NumberType.RATIONAL) { + // round is numerator + denominator // 2 * denominator + const rational = n as SchemeRational; + const numerator = rational.getNumerator(); + const denominator = rational.getDenominator(); + return SchemeInteger.build((numerator + denominator / 2n) / denominator); + } + return SchemeReal.build(Math.round(n.coerce())); +}; + +export const make$45$rectangular = ( + a: SchemeNumber, + b: SchemeNumber, +): SchemeNumber => { + if (!is_number(a) || !is_number(b)) { + throw new Error("make-rectangular: expected numbers"); + } + if (!is_real(a) || !is_real(b)) { + // complex number case + throw new Error("make-rectangular: expected real numbers"); + } + return SchemeComplex.build( + a as SchemeReal | SchemeRational | SchemeInteger, + b as SchemeReal | SchemeRational | SchemeInteger, + ); +}; + +export const make$45$polar = ( + a: SchemeNumber, + b: SchemeNumber, +): SchemeNumber => { + if (!is_number(a) || !is_number(b)) { + throw new Error("make-polar: expected numbers"); + } + if (!is_real(a) || !is_real(b)) { + // complex number case + throw new Error("make-polar: expected real numbers"); + } + return SchemePolar.build( + a.promote(NumberType.REAL) as SchemeReal, + b.promote(NumberType.REAL) as SchemeReal, + ).toCartesian(); +}; + +export const real$45$part = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("real-part: expected number"); + } + if (!is_real(n)) { + // complex number case + return (n as SchemeComplex).getReal(); + } + return n; +}; + +export const imag$45$part = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("imag-part: expected number"); + } + if (!is_real(n)) { + // complex number case + return (n as SchemeComplex).getImaginary(); + } + return SchemeInteger.EXACT_ZERO; +}; + +export const magnitude = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("magnitude: expected number"); + } + if (!is_real(n)) { + // complex number case + return (n as SchemeComplex).toPolar().magnitude; + } + // abs is not defined here so we should just use direct comparison + if (atomic_less_than(n, SchemeInteger.EXACT_ZERO)) { + return atomic_negate(n); + } + return n; +}; + +export const angle = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("angle: expected number"); + } + if (!is_real(n)) { + // complex number case + return (n as SchemeComplex).toPolar().angle; + } + if (atomic_less_than(n, SchemeInteger.EXACT_ZERO)) { + return PI; + } + return SchemeInteger.EXACT_ZERO; +}; From 4b85ae175107b6fb175424a9cd752195fa63faf9 Mon Sep 17 00:00:00 2001 From: s-kybound Date: Sun, 14 Apr 2024 22:48:13 +0800 Subject: [PATCH 2/4] implement the rest of the stdlib --- src/stdlib/base.ts | 12 +++ src/stdlib/core-math.ts | 178 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+) diff --git a/src/stdlib/base.ts b/src/stdlib/base.ts index 156f973..5efe5da 100644 --- a/src/stdlib/base.ts +++ b/src/stdlib/base.ts @@ -197,6 +197,10 @@ export const $62$$61$: Function = ( export const zero$63$: Function = (n: core.SchemeNumber) => $61$(n, make_number(0)); +export const infinity$63$: Function = (n: core.SchemeNumber) => + $61$(n, core.SchemeReal.INFINITY) || $61$(n, core.SchemeReal.NEG_INFINITY); +export const nan$63$: Function = (n: core.SchemeNumber) => + n === core.SchemeReal.NAN; export const positive$63$: Function = (n: core.SchemeNumber) => $62$(n, make_number(0)); export const negative$63$: Function = (n: core.SchemeNumber) => @@ -408,6 +412,14 @@ export const lcm: Function = (...vals: core.SchemeInteger[]) => { return vals.reduce(atomic_lcm); }; +export const odd$63$: Function = core.odd$63$; +export const even$63$: Function = core.even$63$; + +export const numerator: Function = core.numerator; +export const denominator: Function = core.denominator; +export const exact: Function = core.exact; +export const inexact: Function = core.inexact; + export const square: Function = (n: core.SchemeNumber) => $42$(n, n); export const expt: Function = core.expt; export const exp: Function = core.exp; diff --git a/src/stdlib/core-math.ts b/src/stdlib/core-math.ts index c954f2b..1e48429 100644 --- a/src/stdlib/core-math.ts +++ b/src/stdlib/core-math.ts @@ -449,6 +449,10 @@ export class SchemeInteger { return SchemeInteger.build(this.value * other.value); } + getBigInt(): bigint { + return this.value; + } + coerce(): number { if (this.value > Number.MAX_SAFE_INTEGER) { return Infinity; @@ -1046,6 +1050,156 @@ export const LOG10E = SchemeReal.build(Math.LOG10E); export const SQRT1_2 = SchemeReal.build(Math.SQRT1_2); // other important functions + +export const numerator = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("numerator: expected number"); + } + if (!is_real(n)) { + // complex number case + // always return an integer + return is_exact(n) ? SchemeInteger.build(1) : SchemeReal.build(1); + } + if (!is_rational(n)) { + // is real number + // get the value of the number + const val = n.coerce(); + // if the value is a defined special case, return accordingly + if (val === Infinity) { + return SchemeReal.build(1); + } + if (val === -Infinity) { + return SchemeReal.build(1); + } + if (isNaN(val)) { + return SchemeReal.NAN; + } + // if the value is an integer, return it + if (Number.isInteger(val)) { + return SchemeReal.build(val); + } + // else if the value is a float, + // multiply it till it becomes an integer + let multiplier = 1; + while (!Number.isInteger(val * multiplier)) { + multiplier *= 10; + } + let numerator = val * multiplier; + let denominator = multiplier; + // simplify the fraction + const gcd = (a: number, b: number): number => { + if (b === 0) { + return a; + } + return gcd(b, a % b); + }; + const divisor = gcd(numerator, denominator); + numerator = numerator / divisor; + return SchemeReal.build(numerator); + } + return SchemeInteger.build( + (n.promote(NumberType.RATIONAL) as SchemeRational).getNumerator(), + ); +}; + +export const denominator = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("denominator: expected number"); + } + if (!is_real(n)) { + // complex number case + // always return an integer + return is_exact(n) ? SchemeInteger.build(1) : SchemeReal.build(1); + } + if (!is_rational(n)) { + // is real number + // get the value of the number + const val = n.coerce(); + // if the value is a defined special case, return accordingly + if (val === Infinity) { + return SchemeReal.INEXACT_ZERO; + } + if (val === -Infinity) { + return SchemeReal.INEXACT_ZERO; + } + if (isNaN(val)) { + return SchemeReal.NAN; + } + // if the value is an integer, return 1 + if (Number.isInteger(val)) { + return SchemeReal.build(1); + } + // else if the value is a float, + // multiply it till it becomes an integer + let multiplier = 1; + while (!Number.isInteger(val * multiplier)) { + multiplier *= 10; + } + let numerator = val * multiplier; + let denominator = multiplier; + // simplify the fraction + const gcd = (a: number, b: number): number => { + if (b === 0) { + return a; + } + return gcd(b, a % b); + }; + const divisor = gcd(numerator, denominator); + denominator = denominator / divisor; + return SchemeReal.build(denominator); + } + return SchemeInteger.build( + (n.promote(NumberType.RATIONAL) as SchemeRational).getDenominator(), + ); +}; + +export const exact = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("exact: expected number"); + } + if (is_exact(n)) { + return n; + } + if (is_real(n)) { + // if the number is a real number, we can convert it to a rational number + // by multiplying it by a power of 10 until it becomes an integer + // and then dividing by the same power of 10 + let multiplier = 1; + let val = n.coerce(); + while (!Number.isInteger(val * multiplier)) { + multiplier *= 10; + } + return SchemeRational.build(val * multiplier, multiplier); + } + // if the number is a complex number, we can convert both the real and imaginary parts + // to exact numbers + return SchemeComplex.build( + exact((n as SchemeComplex).getReal()) as SchemeInteger | SchemeRational, + exact((n as SchemeComplex).getImaginary()) as + | SchemeInteger + | SchemeRational, + ); +}; + +export const inexact = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("inexact: expected number"); + } + if (is_inexact(n)) { + return n; + } + if (is_real(n)) { + // if the number is a real number, we can convert it to a float + return SchemeReal.build(n.coerce()); + } + // if the number is a complex number, we can convert both the real and imaginary parts + // to inexact numbers + return SchemeComplex.build( + inexact((n as SchemeComplex).getReal()) as SchemeReal, + inexact((n as SchemeComplex).getImaginary()) as SchemeReal, + ); +}; + // for now, exponentials, square roots and the like will be treated as // inexact functions, and will return inexact results. this allows us to // leverage on the inbuilt javascript Math library. @@ -1485,3 +1639,27 @@ export const angle = (n: SchemeNumber): SchemeNumber => { } return SchemeInteger.EXACT_ZERO; }; + +export const odd$63$ = (n: SchemeInteger): boolean => { + if (!is_number(n)) { + throw new Error("odd?: expected integer"); + } + + if (!is_integer(n)) { + throw new Error("odd?: expected integer"); + } + + return n.getBigInt() % 2n === 1n; +}; + +export const even$63$ = (n: SchemeInteger): boolean => { + if (!is_number(n)) { + throw new Error("even?: expected integer"); + } + + if (!is_integer(n)) { + throw new Error("even?: expected integer"); + } + + return n.getBigInt() % 2n === 0n; +}; From f373827212e42c1ee9b8ebaaf4ece64b9877017b Mon Sep 17 00:00:00 2001 From: s-kybound Date: Sun, 14 Apr 2024 23:29:56 +0800 Subject: [PATCH 3/4] change dependabot ecosystem --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index fe3de70..5f0889c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,7 +5,7 @@ version: 2 updates: - - package-ecosystem: "yarn" # See documentation for possible values + - package-ecosystem: "npm" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "weekly" From d9bd5a6572c02781be00e137021f0b79d3367737 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 Apr 2024 23:32:43 +0800 Subject: [PATCH 4/4] Bump js-base64 from 3.7.6 to 3.7.7 (#14) Bumps [js-base64](https://github.com/dankogai/js-base64) from 3.7.6 to 3.7.7. - [Commits](https://github.com/dankogai/js-base64/compare/3.7.6...3.7.7) --- updated-dependencies: - dependency-name: js-base64 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kyriel Abad --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ffed1eb..1b2df4c 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@types/estree": "^1.0.0", "acorn": "^8.8.2", "acorn-walk": "^8.2.0", - "js-base64": "^3.7.5" + "js-base64": "^3.7.7" }, "scripts": { "build-libs": "npx ts-node ./src/compile-libs.ts", diff --git a/yarn.lock b/yarn.lock index 11c0c89..4d7d0c8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3309,10 +3309,10 @@ jest@^29.7.0: import-local "^3.0.2" jest-cli "^29.7.0" -js-base64@^3.7.5: - version "3.7.6" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.6.tgz#6ccb5d761b48381fd819f9ce04998866dbcbbc99" - integrity sha512-NPrWuHFxFUknr1KqJRDgUQPexQF0uIJWjeT+2KjEePhitQxQEx5EJBG1lVn5/hc8aLycTpXrDOgPQ6Zq+EDiTA== +js-base64@^3.7.7: + version "3.7.7" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.7.tgz#e51b84bf78fbf5702b9541e2cb7bfcb893b43e79" + integrity sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw== js-tokens@^4.0.0: version "4.0.0"