UNPKG

js-slang

Version:

Javascript-based implementations of Source, written in Typescript

1,307 lines 49 kB
"use strict"; // The core library of scm-slang, // different from the base library, // this library contains all methods required // for the language to function properly. Object.defineProperty(exports, "__esModule", { value: true }); exports.sin = exports.sqrt = exports.log = exports.exp = exports.expt = exports.inexact = exports.exact = exports.denominator = exports.numerator = exports.SQRT1_2 = exports.LOG10E = exports.LOG2E = exports.LN10 = exports.LN2 = exports.SQRT2 = exports.E = exports.PI = exports.atomic_divide = exports.atomic_subtract = exports.atomic_multiply = exports.atomic_add = exports.atomic_greater_than_or_equals = exports.atomic_greater_than = exports.atomic_less_than_or_equals = exports.atomic_less_than = exports.atomic_equals = exports.atomic_inverse = exports.atomic_negate = exports.is_inexact = exports.is_exact = exports.is_complex = exports.is_real = exports.is_rational = exports.is_integer = exports.is_number = exports.coerce_to_number = exports.nan = exports.infinity = exports.SchemeComplex = exports.SchemeReal = exports.SchemeRational = exports.SchemeInteger = exports.make_number = exports.stringIsSchemeNumber = exports.isComplex = exports.isReal = exports.isRational = exports.isInteger = exports.Match = exports.NumberType = void 0; exports.even$63$ = exports.odd$63$ = exports.angle = exports.magnitude = exports.imag$45$part = exports.real$45$part = exports.make$45$polar = exports.make$45$rectangular = exports.round = exports.truncate = exports.ceiling = exports.floor = exports.atan = exports.acos = exports.asin = exports.tan = exports.cos = void 0; // define here the functions used to check and split the number into its parts var NumberType; (function (NumberType) { NumberType[NumberType["INTEGER"] = 1] = "INTEGER"; NumberType[NumberType["RATIONAL"] = 2] = "RATIONAL"; NumberType[NumberType["REAL"] = 3] = "REAL"; NumberType[NumberType["COMPLEX"] = 4] = "COMPLEX"; })(NumberType = exports.NumberType || (exports.NumberType = {})); class Match { constructor(result) { this.result = result; } } exports.Match = Match; class IntegerMatch extends Match { constructor(result, value) { super(result); this.result = result; this.value = value; } isSigned() { return this.result ? this.value[0] === "+" || this.value[0] === "-" : false; } build() { return SchemeInteger.build(this.value); } } class RationalMatch extends Match { constructor(result, numerator, denominator) { super(result); this.result = result; this.numerator = numerator; this.denominator = denominator; } build() { return SchemeRational.build(this.numerator, this.denominator); } } class RealMatch extends Match { constructor(result, integer, decimal, exponent) { super(result); this.result = result; this.integer = integer; this.decimal = decimal; this.exponent = exponent; } build() { if (this.integer?.includes("inf")) { return this.integer.includes("-") ? SchemeReal.NEG_INFINITY : SchemeReal.INFINITY; } if (this.integer?.includes("nan")) { return SchemeReal.NAN; } // recursively build the exponent let exponent = (this.exponent ? this.exponent.build() : SchemeReal.INEXACT_ZERO).coerce(); // we are assured that either part exists let value = Number((this.integer ? this.integer : "0") + "." + (this.decimal ? this.decimal : "0")); // apply the exponent value *= Math.pow(10, exponent); return SchemeReal.build(value); } } class ComplexMatch extends Match { constructor(result, real, sign, imaginary) { super(result); this.result = result; this.real = real; this.sign = sign; this.imaginary = imaginary; } build() { const real = this.real ? this.real.build() : SchemeInteger.EXACT_ZERO; const imaginary = this.imaginary.build(); if (this.sign && this.sign === "-") { return SchemeComplex.build(real, imaginary.negate()); } return SchemeComplex.build(real, imaginary); } } // these are used to determine the type of the number and to separate it into its parts as well function isInteger(value) { // <integer> = [+-]?<digit>+ // check if the value is an integer. if it is, return true and the value. // if not, return false and an empty array. const integerRegex = new RegExp(`^([+-]?)(\\d+)$`); const match = integerRegex.exec(value); if (match) { return new IntegerMatch(true, match[0]); } return new IntegerMatch(false); } exports.isInteger = isInteger; function isRational(value) { // <rational> = <integer>/<integer> // both sides of the rational should parse as integers // we can split the rational into two parts and check if both are integers // make sure there is a / const count = (value.match(/\//g) || []).length; if (count !== 1) { return new RationalMatch(false); } const parts = value.split("/"); if (parts.length !== 2) { return new RationalMatch(false); } const [numerator, denominator] = parts; const numeratorMatch = isInteger(numerator); const denominatorMatch = isInteger(denominator); if (!(numeratorMatch.result && denominatorMatch.result)) { return new RationalMatch(false); } return new RationalMatch(true, numerator, denominator); } exports.isRational = isRational; function isReal(value) { // <real> = <basic> | <extended> // <basic>: [+-]?a.b | [+-]?a | [+-]?.b | [+-]?a. // <extended>: <basic>[eE]<integer | rational | real> // where a = <digit>+ | inf | nan // b = <digit>+ // // keep in mind that the value matches an integer too! but // by the point of time this is called, we have already checked for an integer function checkBasicReal(value) { // checks if the value is one of the 4 forms of special numbers function isSpecialNumber(value) { return (value === "+inf.0" || value === "-inf.0" || value === "+nan.0" || value === "-nan.0"); } // check if the value is a special number if (isSpecialNumber(value)) { return new RealMatch(true, value); } // check for the presence of a dot const count = (value.match(/\./g) || []).length; if (count > 1) { return new RealMatch(false); } if (count === 0) { const result = isInteger(value); return new RealMatch(result.result, result.value); } // check for a basic real number const [integerPart, decimalPart] = value.split("."); const integerMatch = isInteger(integerPart); const decimalMatch = isInteger(decimalPart); const properInteger = integerMatch.result || integerPart === ""; const properDecimal = decimalMatch.result || decimalPart === ""; // if the integer part is just a sign, the decimal part should be non-empty if (integerPart === "+" || integerPart === "-") { if (decimalPart === "") { return new RealMatch(false); } return new RealMatch(true, `${integerPart}0`, value); } // at least one of the parts should be non-empty if (!((integerMatch.result && properDecimal) || (properInteger && decimalMatch.result))) { return new RealMatch(false); } // if there is a decimal match, there should have no sign if (decimalMatch.result && decimalMatch.isSigned()) { return new RealMatch(false); } return new RealMatch(true, integerMatch.value, decimalMatch.value); } function checkExtendedReal(value) { // split the value into two parts by e/E const first_e_index = value.indexOf("e"); const first_E_index = value.indexOf("E"); if (first_e_index === -1 && first_E_index === -1) { return new RealMatch(false); } const exponentIndex = first_e_index === -1 ? first_E_index : first_e_index; const basicRealPart = value.substring(0, exponentIndex); const exponentPart = value.substring(exponentIndex + 1); // both should not be empty if (basicRealPart === "" || exponentPart == "") { return new RealMatch(false); } // parse each part const basicRealMatch = checkBasicReal(basicRealPart); if (!basicRealMatch.result) { return new RealMatch(false); } // match the exponent part across types up to real const exponentMatch = universalMatch(exponentPart, NumberType.REAL); if (!exponentMatch.result) { return new RealMatch(false); } return new RealMatch(true, basicRealMatch.integer, basicRealMatch.decimal, exponentMatch); } // check for the presence of e/E const count = (value.match(/[eE]/g) || []).length; if (count === 0) { // check for a basic real number return checkBasicReal(value); } // check for an extended real number return checkExtendedReal(value); } exports.isReal = isReal; function isComplex(value) { // <basic-num> = <integer> | <rational> | <real> // <complex> = <basic-num>[+-]<basic-num>i // check if the value is a complex number. if it is, return true and the value. // if not, return a failed match. const count = (value.match(/i/g) || []).length; if (count < 1) { return new ComplexMatch(false); } if (value[value.length - 1] !== "i") { return new ComplexMatch(false); } // find the first + or - that is not at the start of the string // this is the split point const splitPoint = value.search(/(?<!^)[+-]/); // if no such point was found, if (splitPoint === -1) { // the value may be purely imaginary let imaginaryPart = value.slice(0, -1); const imaginaryMatch = universalMatch(imaginaryPart, NumberType.REAL); if (imaginaryMatch.result) { return new ComplexMatch(true, undefined, undefined, imaginaryMatch); } return new ComplexMatch(false); } const realPart = value.slice(0, splitPoint); let imaginaryPart = value.slice(splitPoint + 1, -1); // if imaginaryPart doesn't start with a sign, add one // this lets us properly parse expressions such as 1+inf.0i // even if the + belongs to the complex number if (imaginaryPart[0] !== "+" && imaginaryPart[0] !== "-") { imaginaryPart = "+" + imaginaryPart; } const realMatch = universalMatch(realPart, NumberType.REAL); const imaginaryMatch = universalMatch(imaginaryPart, NumberType.REAL); if (!(realMatch.result && imaginaryMatch.result)) { return new ComplexMatch(false); } return new ComplexMatch(true, realMatch, value[splitPoint], imaginaryMatch); } exports.isComplex = isComplex; // tests the value across all possible types // only limited by the finalWillingType of function universalMatch(value, finalWillingType) { const integerMatch = isInteger(value); if (integerMatch.result && finalWillingType >= NumberType.INTEGER) { return integerMatch; } const rationalMatch = isRational(value); if (rationalMatch.result && finalWillingType >= NumberType.RATIONAL) { return rationalMatch; } const realMatch = isReal(value); if (realMatch.result && finalWillingType >= NumberType.REAL) { return realMatch; } const complexMatch = isComplex(value); if (complexMatch.result && finalWillingType >= NumberType.COMPLEX) { return complexMatch; } return new IntegerMatch(false); } // for the lexer. function stringIsSchemeNumber(value) { const match = universalMatch(value, NumberType.COMPLEX); return match.result; } exports.stringIsSchemeNumber = stringIsSchemeNumber; // Each class has a numberType property that is used to determine the type of the number. // If another instance's numbertype is higher in an operation, it will "promote" itself to the higher type. // Each class also has a convert method that converts the number back into a javascript number. // This is used when the number is used in a context where a javascript number is expected. // If used in contexts where the values are too extreme for a javascript number, it will throw an error. // This includes attempting to convert a complex number to a javascript number. // If a simplified rational number has a denominator of 1, it will convert to an integer. // We are assured that the string passed to this function is a valid number. let make_number = (value) => { const match = universalMatch(value, NumberType.COMPLEX); if (!match.result) { throw new Error("Invalid number"); } return match.build(); }; exports.make_number = make_number; class SchemeInteger { constructor(value) { this.numberType = NumberType.INTEGER; this.value = value; } // Factory method for creating a new SchemeInteger instance. // Force prevents automatic downcasting to a lower type. static build(value, _force = false) { const val = BigInt(value); if (val === 0n) { return SchemeInteger.EXACT_ZERO; } return new SchemeInteger(val); } promote(nType) { switch (nType) { case NumberType.INTEGER: return this; case NumberType.RATIONAL: return SchemeRational.build(this.value, 1n, true); case NumberType.REAL: return SchemeReal.build(this.coerce(), true); case NumberType.COMPLEX: return SchemeComplex.build(this, SchemeInteger.EXACT_ZERO, true); } } equals(other) { return other instanceof SchemeInteger && this.value === other.value; } greaterThan(other) { return this.value > other.value; } negate() { if (this === SchemeInteger.EXACT_ZERO) { return this; } return SchemeInteger.build(-this.value); } multiplicativeInverse() { if (this === SchemeInteger.EXACT_ZERO) { throw new Error("Division by zero"); } return SchemeRational.build(1n, this.value, false); } add(other) { return SchemeInteger.build(this.value + other.value); } multiply(other) { return SchemeInteger.build(this.value * other.value); } getBigInt() { return this.value; } coerce() { if (this.value > Number.MAX_SAFE_INTEGER) { return Infinity; } if (this.value < Number.MIN_SAFE_INTEGER) { return -Infinity; } return Number(this.value); } toString() { return this.value.toString(); } } exports.SchemeInteger = SchemeInteger; SchemeInteger.EXACT_ZERO = new SchemeInteger(0n); class SchemeRational { constructor(numerator, denominator) { this.numberType = NumberType.RATIONAL; this.numerator = numerator; this.denominator = denominator; } // Builds a rational number. // Force prevents automatic downcasting to a lower type. static build(numerator, denominator, force = false) { return SchemeRational.simplify(BigInt(numerator), BigInt(denominator), force); } static simplify(numerator, denominator, force = false) { const gcd = (a, b) => { if (b === 0n) { return a; } return gcd(b, a.valueOf() % b.valueOf()); }; const divisor = gcd(numerator, denominator); const numeratorSign = numerator < 0n ? -1n : 1n; const denominatorSign = denominator < 0n ? -1n : 1n; // determine the sign of the result const sign = numeratorSign * denominatorSign; // remove the sign from the numerator and denominator numerator = numerator * numeratorSign; denominator = denominator * denominatorSign; // if the denominator is 1, we can return an integer if (denominator === 1n && !force) { return SchemeInteger.build(sign * numerator); } return new SchemeRational((sign * numerator) / divisor, denominator / divisor); } getNumerator() { return this.numerator; } getDenominator() { return this.denominator; } promote(nType) { switch (nType) { case NumberType.RATIONAL: return this; case NumberType.REAL: return SchemeReal.build(this.coerce(), true); case NumberType.COMPLEX: return SchemeComplex.build(this, SchemeInteger.EXACT_ZERO, true); default: throw new Error("Unable to demote rational"); } } equals(other) { return (other instanceof SchemeRational && this.numerator === other.numerator && this.denominator === other.denominator); } greaterThan(other) { return (this.numerator * other.denominator > other.numerator * this.denominator); } negate() { return SchemeRational.build(-this.numerator, this.denominator); } multiplicativeInverse() { if (this.numerator === 0n) { throw new Error("Division by zero"); } return SchemeRational.build(this.denominator, this.numerator); } add(other) { const newNumerator = this.numerator * other.denominator + other.numerator * this.denominator; const newDenominator = this.denominator * other.denominator; return SchemeRational.build(newNumerator, newDenominator); } multiply(other) { const newNumerator = this.numerator * other.numerator; const newDenominator = this.denominator * other.denominator; return SchemeRational.build(newNumerator, newDenominator); } coerce() { const workingNumerator = this.numerator < 0n ? -this.numerator : this.numerator; let converterDenominator = this.denominator; // we can take the whole part directly const wholePart = Number(workingNumerator / converterDenominator); if (wholePart > Number.MAX_VALUE) { return this.numerator < 0n ? -Infinity : Infinity; } // remainder should be lossily converted below safe levels let remainder = workingNumerator % converterDenominator; // we lossily convert both values below safe number thresholds while (remainder > Number.MAX_SAFE_INTEGER || converterDenominator > Number.MAX_SAFE_INTEGER) { remainder = remainder / 2n; converterDenominator = converterDenominator / 2n; } // coerce the now safe parts into a remainder number const remainderPart = Number(remainder) / Number(converterDenominator); return this.numerator < 0n ? -(wholePart + remainderPart) : wholePart + remainderPart; } toString() { return `${this.numerator}/${this.denominator}`; } } exports.SchemeRational = SchemeRational; // it is allowable to represent the Real number using // float/double representation, and so we shall do that. // the current schemeReal implementation is fully based // on JavaScript numbers. class SchemeReal { static build(value, _force = false) { if (value === Infinity) { return SchemeReal.INFINITY; } else if (value === -Infinity) { return SchemeReal.NEG_INFINITY; } else if (isNaN(value)) { return SchemeReal.NAN; } else if (value === 0) { return SchemeReal.INEXACT_ZERO; } else if (value === -0) { return SchemeReal.INEXACT_NEG_ZERO; } return new SchemeReal(value); } constructor(value) { this.numberType = NumberType.REAL; this.value = value; } promote(nType) { switch (nType) { case NumberType.REAL: return this; case NumberType.COMPLEX: return SchemeComplex.build(this, SchemeInteger.EXACT_ZERO, true); default: throw new Error("Unable to demote real"); } } equals(other) { return other instanceof SchemeReal && this.value === other.value; } greaterThan(other) { return this.value > other.value; } negate() { return SchemeReal.build(-this.value); } multiplicativeInverse() { if (this === SchemeReal.INEXACT_ZERO || this === SchemeReal.INEXACT_NEG_ZERO) { throw new Error("Division by zero"); } return SchemeReal.build(1 / this.value); } add(other) { return SchemeReal.build(this.value + other.value); } multiply(other) { return SchemeReal.build(this.value * other.value); } coerce() { return this.value; } toString() { if (this === SchemeReal.INFINITY) { return "+inf.0"; } if (this === SchemeReal.NEG_INFINITY) { return "-inf.0"; } if (this === SchemeReal.NAN) { return "+nan.0"; } return this.value.toString(); } } exports.SchemeReal = SchemeReal; SchemeReal.INEXACT_ZERO = new SchemeReal(0); SchemeReal.INEXACT_NEG_ZERO = new SchemeReal(-0); SchemeReal.INFINITY = new SchemeReal(Infinity); SchemeReal.NEG_INFINITY = new SchemeReal(-Infinity); SchemeReal.NAN = new SchemeReal(NaN); class SchemeComplex { static build(real, imaginary, force = false) { return SchemeComplex.simplify(new SchemeComplex(real, imaginary), force); } constructor(real, imaginary) { this.numberType = NumberType.COMPLEX; this.real = real; this.imaginary = imaginary; } static simplify(complex, force) { if (!force && atomic_equals(complex.imaginary, SchemeInteger.EXACT_ZERO)) { return complex.real; } return complex; } promote(nType) { switch (nType) { case NumberType.COMPLEX: return this; default: throw new Error("Unable to demote complex"); } } negate() { return SchemeComplex.build(this.real.negate(), this.imaginary.negate()); } equals(other) { return (atomic_equals(this.real, other.real) && atomic_equals(this.imaginary, other.imaginary)); } greaterThan(other) { return (atomic_greater_than(this.real, other.real) && atomic_greater_than(this.imaginary, other.imaginary)); } multiplicativeInverse() { // inverse of a + bi = a - bi / a^2 + b^2 // in this case, we use a / a^2 + b^2 and -b / a^2 + b^2 as the new values required const denominator = atomic_add(atomic_multiply(this.real, this.real), atomic_multiply(this.imaginary, this.imaginary)); return SchemeComplex.build(atomic_multiply(denominator.multiplicativeInverse(), this.real), atomic_multiply(denominator.multiplicativeInverse(), this.imaginary.negate())); } add(other) { return SchemeComplex.build(atomic_add(this.real, other.real), atomic_add(this.imaginary, other.imaginary)); } multiply(other) { // (a + bi) * (c + di) = (ac - bd) + (ad + bc)i const realPart = atomic_subtract(atomic_multiply(this.real, other.real), atomic_multiply(this.imaginary, other.imaginary)); const imaginaryPart = atomic_add(atomic_multiply(this.real, other.imaginary), atomic_multiply(this.imaginary, other.real)); return SchemeComplex.build(realPart, imaginaryPart); } getReal() { return this.real; } getImaginary() { return this.imaginary; } coerce() { throw new Error("Cannot coerce a complex number to a javascript number"); } toPolar() { // force both the real and imaginary parts to be inexact const real = this.real.promote(NumberType.REAL); const imaginary = this.imaginary.promote(NumberType.REAL); // schemeReals can be reasoned with using the same logic as javascript numbers // r = sqrt(a^2 + b^2) const magnitude = SchemeReal.build(Math.sqrt(real.coerce() * real.coerce() + imaginary.coerce() * imaginary.coerce())); // theta = atan(b / a) const angle = SchemeReal.build(Math.atan2(imaginary.coerce(), real.coerce())); return SchemePolar.build(magnitude, angle); } toString() { return `${this.real}+${this.imaginary}i`; } } exports.SchemeComplex = SchemeComplex; // an alternative form of the complex number. // only used in intermediate steps, will be converted back at the end of the operation. // current scm-slang will force any polar complex numbers to be made // inexact, hence we opt to limit the use of polar form as much as possible. class SchemePolar { constructor(magnitude, angle) { this.magnitude = magnitude; this.angle = angle; } static build(magnitude, angle) { return new SchemePolar(magnitude, angle); } // converts the polar number back to a cartesian complex number toCartesian() { // a + bi = r * cos(theta) + r * sin(theta)i // a = r * cos(theta) // b = r * sin(theta) const real = SchemeReal.build(this.magnitude.coerce() * Math.cos(this.angle.coerce())); const imaginary = SchemeReal.build(this.magnitude.coerce() * Math.sin(this.angle.coerce())); return SchemeComplex.build(real, imaginary); } } exports.infinity = SchemeReal.INFINITY; exports.nan = SchemeReal.NAN; // this function is used to convert a number to a javascript number. // it should only be limited to numbers used for indexing, integers. function coerce_to_number(a) { return a.coerce(); } exports.coerce_to_number = coerce_to_number; // these functions deal with checking the type of a number. function is_number(a) { return (a.numberType !== undefined && Object.values(NumberType).includes(a.numberType)); } exports.is_number = is_number; function is_integer(a) { return is_number(a) && a.numberType <= 1; } exports.is_integer = is_integer; function is_rational(a) { return is_number(a) && a.numberType <= 2; } exports.is_rational = is_rational; function is_real(a) { return is_number(a) && a.numberType <= 3; } exports.is_real = is_real; function is_complex(a) { return is_number(a) && a.numberType <= 4; } exports.is_complex = is_complex; function is_exact(a) { // if the number is a complex number, we need to check both the real and imaginary parts return is_number(a) ? a.numberType === 4 ? is_exact(a.real) && is_exact(a.imaginary) : a.numberType <= 2 : false; } exports.is_exact = is_exact; function is_inexact(a) { // defined in terms of is_exact return is_number(a) && !is_exact(a); } exports.is_inexact = is_inexact; // the functions below are used to perform operations on numbers function simplify(a) { switch (a.numberType) { case NumberType.INTEGER: return a; case NumberType.RATIONAL: return a.getDenominator() === 1n ? SchemeInteger.build(a.getNumerator()) : a; case NumberType.REAL: return a; case NumberType.COMPLEX: // safe to cast as simplify never promotes a number return SchemeComplex.build(simplify(a.getReal()), simplify(a.getImaginary())); } } /** * This function takes two numbers and brings them to the same level. */ function equalify(a, b) { if (a.numberType > b.numberType) { return [a, b.promote(a.numberType)]; } else if (a.numberType < b.numberType) { return [a.promote(b.numberType), b]; } return [a, b]; } function atomic_negate(a) { return a.negate(); } exports.atomic_negate = atomic_negate; function atomic_inverse(a) { return a.multiplicativeInverse(); } exports.atomic_inverse = atomic_inverse; function atomic_equals(a, b) { const [newA, newB] = equalify(a, b); // safe to cast as we are assured they are of the same type return newA.equals(newB); } exports.atomic_equals = atomic_equals; function atomic_less_than(a, b) { return !atomic_greater_than(a, b) && !atomic_equals(a, b); } exports.atomic_less_than = atomic_less_than; function atomic_less_than_or_equals(a, b) { return !atomic_greater_than(a, b); } exports.atomic_less_than_or_equals = atomic_less_than_or_equals; function atomic_greater_than(a, b) { const [newA, newB] = equalify(a, b); // safe to cast as we are assured they are of the same type return newA.greaterThan(newB); } exports.atomic_greater_than = atomic_greater_than; function atomic_greater_than_or_equals(a, b) { return atomic_greater_than(a, b) || atomic_equals(a, b); } exports.atomic_greater_than_or_equals = atomic_greater_than_or_equals; function atomic_add(a, b) { const [newA, newB] = equalify(a, b); // safe to cast as we are assured they are of the same type return simplify(newA.add(newB)); } exports.atomic_add = atomic_add; function atomic_multiply(a, b) { const [newA, newB] = equalify(a, b); // safe to cast as we are assured they are of the same type return simplify(newA.multiply(newB)); } exports.atomic_multiply = atomic_multiply; function atomic_subtract(a, b) { return atomic_add(a, atomic_negate(b)); } exports.atomic_subtract = atomic_subtract; function atomic_divide(a, b) { return atomic_multiply(a, atomic_inverse(b)); } exports.atomic_divide = atomic_divide; /** * Important constants */ exports.PI = SchemeReal.build(Math.PI); exports.E = SchemeReal.build(Math.E); exports.SQRT2 = SchemeReal.build(Math.SQRT2); exports.LN2 = SchemeReal.build(Math.LN2); exports.LN10 = SchemeReal.build(Math.LN10); exports.LOG2E = SchemeReal.build(Math.LOG2E); exports.LOG10E = SchemeReal.build(Math.LOG10E); exports.SQRT1_2 = SchemeReal.build(Math.SQRT1_2); // other important functions const numerator = (n) => { 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, b) => { 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).getNumerator()); }; exports.numerator = numerator; const denominator = (n) => { 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, b) => { 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).getDenominator()); }; exports.denominator = denominator; const exact = (n) => { 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((0, exports.exact)(n.getReal()), (0, exports.exact)(n.getImaginary())); }; exports.exact = exact; const inexact = (n) => { 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((0, exports.inexact)(n.getReal()), (0, exports.inexact)(n.getImaginary())); }; exports.inexact = inexact; // 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. // additional logic is required to handle complex numbers, which we can do with // our polar form representation. const expt = (n, e) => { if (!is_number(n) || !is_number(e)) { throw new Error("expt: expected numbers"); } if (!is_real(n) || !is_real(e)) { // complex number case // we can convert both parts to polar form and use the // polar form exponentiation formula. // given a * e^(bi) and c * e^(di), // (a * e^(bi)) ^ (c * e^(di)) can be represented by // the general formula for complex exponentiation: // (a^c * e^(-bd)) * e^(i(bc * ln(a) + ad)) // convert both numbers to polar form const nPolar = n.promote(NumberType.COMPLEX).toPolar(); const ePolar = e.promote(NumberType.COMPLEX).toPolar(); const a = nPolar.magnitude.coerce(); const b = nPolar.angle.coerce(); const c = ePolar.magnitude.coerce(); const d = ePolar.angle.coerce(); // we can construct a new polar form following the formula above const mag = SchemeReal.build(a ** c * Math.E ** (-b * d)); const angle = SchemeReal.build(b * c * Math.log(a) + a * d); return SchemePolar.build(mag, angle).toCartesian(); } // coerce both numbers to javascript numbers const base = n.coerce(); const exponent = e.coerce(); // there are probably cases here i am not considering yet. // for now, we will just use the javascript Math library and hope for the best. return SchemeReal.build(Math.pow(base, exponent)); }; exports.expt = expt; const exp = (n) => { if (!is_number(n)) { throw new Error("exp: expected number"); } if (!is_real(n)) { // complex number case throw new Error("exp: expected real number"); } return SchemeReal.build(Math.exp(n.coerce())); }; exports.exp = exp; const log = (n, base = exports.E) => { if (!is_number(n) || !is_number(base)) { throw new Error("log: expected numbers"); } if (!is_real(n) || !is_real(base)) { // complex number case // we can convert both parts to polar form and use the // polar form logarithm formula. // where log(a * e^(bi)) = log(a) + bi // and log(c * e^(di)) = log(c) + di // and so result is log(a) + bi / log(c) + di // which is just (log(a) - log(c)) + (b / d) i // convert both numbers to polar form const nPolar = n.promote(NumberType.COMPLEX).toPolar(); const basePolar = base.promote(NumberType.COMPLEX).toPolar(); const a = nPolar.magnitude.coerce(); const b = nPolar.angle.coerce(); const c = basePolar.magnitude.coerce(); const d = basePolar.angle.coerce(); return SchemeComplex.build(SchemeReal.build(Math.log(a) - Math.log(c)), SchemeReal.build(b / d)); } return SchemeReal.build(Math.log(n.coerce()) / Math.log(base.coerce())); }; exports.log = log; const sqrt = (n) => { if (!is_number(n)) { throw new Error("sqrt: expected number"); } if (!is_real(n)) { // complex number case const polar = n.promote(NumberType.COMPLEX).toPolar(); const mag = polar.magnitude; const angle = polar.angle; // the square root of a complex number is given by // the square root of the magnitude and half the angle const newMag = (0, exports.sqrt)(mag); const newAngle = SchemeReal.build(angle.coerce() / 2); return SchemePolar.build(newMag, newAngle).toCartesian(); } let value = n.coerce(); if (value < 0) { return SchemeComplex.build(SchemeReal.INEXACT_ZERO, SchemeReal.build(Math.sqrt(-value))); } return SchemeReal.build(Math.sqrt(n.coerce())); }; exports.sqrt = sqrt; const sin = (n) => { if (!is_number(n)) { throw new Error("sin: expected number"); } if (!is_real(n)) { // complex number case // we can use euler's formula to find sin(x) for a complex number x = a + bi // e^(ix) = cos(x) + i * sin(x) // that can be rearranged into // sin(x) = (e^(ix) - e^(-ix)) / 2i // and finally into // sin(x) = (sin(a) * (e^(-b) + e^(b)) / 2) + i * (cos(a) * (e^(-b) - e^(b)) / 2) const complex = n.promote(NumberType.COMPLEX); const real = complex.getReal(); const imaginary = complex.getImaginary(); const a = real.coerce(); const b = imaginary.coerce(); return SchemeComplex.build(SchemeReal.build((Math.sin(a) * (Math.exp(-b) + Math.exp(b))) / 2), SchemeReal.build((Math.cos(a) * (Math.exp(-b) - Math.exp(b))) / 2)); } return SchemeReal.build(Math.sin(n.coerce())); }; exports.sin = sin; const cos = (n) => { if (!is_number(n)) { throw new Error("cos: expected number"); } if (!is_real(n)) { // complex number case // we can use euler's formula to find cos(x) for a complex number x = a + bi // e^(ix) = cos(x) + i * sin(x) // that can be rearranged into // cos(x) = (e^(ix) + e^(-ix)) / 2 // and finally into // cos(x) = (cos(a) * (e^(-b) + e^(b)) / 2) - i * (sin(a) * (e^(-b) - e^(b)) / 2) const complex = n.promote(NumberType.COMPLEX); const real = complex.getReal(); const imaginary = complex.getImaginary(); const a = real.coerce(); const b = imaginary.coerce(); return SchemeComplex.build(SchemeReal.build((Math.cos(a) * (Math.exp(-b) + Math.exp(b))) / 2), SchemeReal.build((-Math.sin(a) * (Math.exp(-b) - Math.exp(b))) / 2)); } return SchemeReal.build(Math.cos(n.coerce())); }; exports.cos = cos; const tan = (n) => { if (!is_number(n)) { throw new Error("tan: expected number"); } if (!is_real(n)) { // complex number case const sinValue = (0, exports.sin)(n); const cosValue = (0, exports.cos)(n); return atomic_divide(sinValue, cosValue); } return SchemeReal.build(Math.tan(n.coerce())); }; exports.tan = tan; const asin = (n) => { if (!is_number(n)) { throw new Error("asin: expected number"); } if (!is_real(n)) { // complex number case // asin(n) = -i * ln(i * n + sqrt(1 - n^2)) // we already have the building blocks needed to compute this const i = SchemeComplex.build(SchemeInteger.EXACT_ZERO, SchemeInteger.build(1)); return atomic_multiply(atomic_negate(i), (0, exports.log)(atomic_add(atomic_multiply(i, n), (0, exports.sqrt)(atomic_subtract(SchemeInteger.build(1), atomic_multiply(n, n)))))); } return SchemeReal.build(Math.asin(n.coerce())); }; exports.asin = asin; const acos = (n) => { if (!is_number(n)) { throw new Error("acos: expected number"); } if (!is_real(n)) { // complex number case // acos(n) = -i * ln(n + sqrt(n^2 - 1)) // again, we have the building blocks needed to compute this const i = SchemeComplex.build(SchemeInteger.EXACT_ZERO, SchemeInteger.build(1)); return atomic_multiply(atomic_negate(i), (0, exports.log)(atomic_add(n, (0, exports.sqrt)(atomic_subtract(atomic_multiply(n, n), SchemeInteger.build(1)))))); } return SchemeReal.build(Math.acos(n.coerce())); }; exports.acos = acos; const atan = (n, m) => { if (!is_number(n)) { throw new Error("atan: expected number"); } if (m !== undefined) { // two argument case, we construct a complex number with n + mi // if neither n nor m are real, it's an error if (!is_real(n) || !is_real(m)) { throw new Error("atan: expected real numbers"); } return (0, exports.atan)(SchemeComplex.build(n, m)); } if (!is_real(n)) { // complex number case // atan(n) = 1/2 * i * ln((1 - i * n) / (1 + i * n)) const i = SchemeComplex.build(SchemeInteger.EXACT_ZERO, SchemeInteger.build(1)); return atomic_multiply( // multiply is associative so the order here doesn't matter atomic_multiply(SchemeRational.build(1, 2), i), (0, exports.log)(atomic_divide(atomic_subtract(SchemeInteger.build(1), atomic_multiply(i, n)), atomic_add(SchemeInteger.build(1), atomic_multiply(i, n))))); } return SchemeReal.build(Math.atan(n.coerce())); }; exports.atan = atan; const floor = (n) => { 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; const numerator = rational.getNumerator(); const denominator = rational.getDenominator(); return SchemeInteger.build(numerator / denominator); } return SchemeReal.build(Math.floor(n.coerce())); }; exports.floor = floor; const ceiling = (n) => { 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; const numerator = rational.getNumerator(); const denominator = rational.getDenominator(); return SchemeInteger.build((numerator + denominator - 1n) / denominator); } return SchemeReal.build(Math.ceil(n.coerce())); }; exports.ceiling = ceiling; const truncate = (n) => { 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; const numerator = rational.getNumerator(); const denominator = rational.getDenominator(); return SchemeInteger.build(numerator / denominator); } return SchemeReal.build(Math.trunc(n.coerce())); }; exports.truncate = truncate; const round = (n) => { 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; const numerator = rational.getNumerator(); const denominator = rational.getDenominator(); return SchemeInteger.build((numerator + denominator / 2n) / denominator); } return SchemeReal.build(Math.round(n.coerce())); }; exports.round = round; const make$45$rectangular = (a, b) => { 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, b); }; exports.make$45$rectangular = make$45$rectangular; const make$45$polar = (a, b) => { 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), b.promote(NumberType.REAL)).toCartesian(); }; exports.make$45$polar = make$45$polar; const real$45$part = (n) => { if (!is_number(n)) { throw new Error("real-part: expected number"); } if (!is_real(n)) { // complex number case return n.getReal(); } return n; }; exports.real$45$part = real$45$part; const imag$45$part = (n) => { if (!is_number(n)) { throw new Error("imag-part: expected number"); } if (!is_real(n)) { // complex number case return n.getImaginary(); } return SchemeInteger.EXACT_ZERO; }; exports.imag$45$part = imag$45$part; const magnitude = (n) => { if (!is_number(n)) { throw new Error("magnitude: expected number"); } if (!is_real(n)) { // complex number case return n.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; }; exports.magnitude = magnitude; const angle = (n) => { if (!is_number(n)) { throw new Error("angle: expected number"); } if (!is_real(n)) { // complex number case return n.toPolar().angle; } if (atomic_less_than(n, SchemeInteger.EXACT_ZERO)) { return exports.PI; } return SchemeInteger.EXACT_ZERO; }; exports.angle = angle; const odd$63$ = (n) => { 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; }; exports.odd$63$ = odd$63$; const even$63$ = (n) => { 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; }; exports.even$63$ = even$63$; //# sourceMappingURL=core-math.js.map