From 53de852d1780bf3cf63468dc8607a274cdddd3e7 Mon Sep 17 00:00:00 2001 From: Jsoto22 Date: Sat, 20 Apr 2024 22:09:03 -0400 Subject: [PATCH] Log and trig functions todo: Unit testing --- src/big-decimal.ts | 93 ++++++++++++++++++++++++++++++++++++++----- src/logarithm.spec.ts | 0 src/logarithm.ts | 76 +++++++++++++++++++++++++++++++++++ src/modulus.ts | 8 ++-- src/pow.ts | 73 ++++++++++++++++----------------- src/trig.spec.ts | 0 src/trig.ts | 50 +++++++++++++++++++++++ src/utils.ts | 73 +++++++++++++++++++++++---------- 8 files changed, 300 insertions(+), 73 deletions(-) create mode 100644 src/logarithm.spec.ts create mode 100644 src/logarithm.ts create mode 100644 src/trig.spec.ts create mode 100644 src/trig.ts diff --git a/src/big-decimal.ts b/src/big-decimal.ts index d154eb9..964000e 100644 --- a/src/big-decimal.ts +++ b/src/big-decimal.ts @@ -8,8 +8,10 @@ import { compareTo, lessThan } from "./compareTo"; import { subtract, negate } from "./subtract"; import { RoundingModes as Modes, RoundingModes } from "./roundingModes"; import { stripTrailingZero } from "./stripTrailingZero"; -import { cbRoot, exp, pow, sqRoot } from "./pow"; -import { Euler, factorial } from "./utils"; +import { cbRoot, pow, sqRoot } from "./pow"; +import { factorial } from "./utils"; +import { cos, sin, tan } from "./trig"; +import { ln, log, ln2, log10, exp, LN2, LN10, LN2E, LN10E, Euler, expm1 } from "./logarithm"; class bigDecimal { private value: string; @@ -226,6 +228,8 @@ class bigDecimal { return new bigDecimal(negate(this.value)); } + // Powers + static pow(base: number|string, exponent: string) { base = bigDecimal.validate(base); exponent = bigDecimal.validate(exponent); @@ -237,6 +241,16 @@ class bigDecimal { return new bigDecimal(pow(this.value, exponent, 32)); } + // Roots + + static get SQRT1_2() { + return sqRoot('.5'); + } + + static get SQRT2() { + return sqRoot('2'); + } + static sqRoot(number: number|string): string { number = bigDecimal.validate(number); return sqRoot(number); @@ -255,18 +269,79 @@ class bigDecimal { return new bigDecimal(cbRoot(this.value)); } - static exp(base: number|string): string { - base = bigDecimal.validate(base); - return exp(base); - } + // Logarithms static get E() { return Euler(32); } - static factorial(base: number|string): string { - base = bigDecimal.validate(base); - return factorial(base); + static get LN2(){ + return LN2 + } + + static get LN10(){ + return LN10 + } + + static get LN2E(){ + return LN2E + } + + static get LN10E(){ + return LN10E + } + + static log2(number: number|string){ + number = bigDecimal.validate(number); + return ln2(number) + } + + static log10(number: number|string){ + number = bigDecimal.validate(number); + return log10(number) + } + + static log1p(number: number|string){ + number = bigDecimal.validate(number); + return log(add('1', number)) + } + + static log(number: number|string){ + number = bigDecimal.validate(number); + return log(number) + } + + static exp(number: number|string): string { + number = bigDecimal.validate(number); + return exp(number); + } + + static expm1(number: number|string): string { + number = bigDecimal.validate(number); + return expm1(number) + } + + // Trig + static sin(number: number|string): string { + number = bigDecimal.validate(number); + return sin(number); + } + + static cos(number: number|string): string { + number = bigDecimal.validate(number); + return cos(number); + } + + static tan(number: number|string): string { + number = bigDecimal.validate(number); + return tan(number); + } + + // Misc. + + static factorial(number: number|string): string { + number = bigDecimal.validate(number); + return factorial(number); } static stripTrailingZero(number) { diff --git a/src/logarithm.spec.ts b/src/logarithm.spec.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/logarithm.ts b/src/logarithm.ts new file mode 100644 index 0000000..f9fda1a --- /dev/null +++ b/src/logarithm.ts @@ -0,0 +1,76 @@ +import { add } from "./add"; +import { lessThan, equals, greaterThan } from "./compareTo"; +import { divide } from "./divide"; +import { multiply } from "./multiply"; +import { pow } from "./pow"; +import { roundOff } from "./round"; +import { subtract } from "./subtract"; +import { factorial, sigma, tolerance } from "./utils"; + +export const LN2 = ln('2'); +export const LN2E = ln2(Euler(32)); +export const LN10 = ln('10'); +export const LN10E = log10(Euler(32)); + +export function Euler(precision: number = 32) { + precision = Math.max(16, precision) + return roundOff(sigma(0, precision, (n: string | number)=>{ + return divide('1', factorial(n), precision + 1) + }), precision); +} + +export function exp(exponent: number | string) { + return pow(Euler(32), exponent) +} + +export function expm1(exponent: number | string) { + return subtract('1', pow(Euler(32), exponent)) +} + +export function ln(x: string | number = 2) { + x = x.toString(); + if (lessThan(x, '0', true)) { + throw "Error: x must be greater than 0"; + } + + if (equals(x, '1')) { + return '0'; // ln(1) = 0 + } + + let result = '0'; + let term = divide(subtract(x, '1'), add(x, '1'), 33); + let i = 0; + while (true) { + i++ + let iteration = subtract(multiply('2', i), '1'); + let next = multiply(divide('1', iteration, 33), pow(term, iteration)) + if (lessThan(next, tolerance(32)) || i == 100) { + return roundOff(multiply('2', add(result, next)), 32) + } + result = add(result, next); + } + + // return multiply('2', result) +} + +export function ln2(x: string | number = 2) { + x = x.toString(); + if (lessThan(x, '0', true)) { + throw "Error: x must be greater than 0"; + } + let result = '0'; + while (greaterThan(x, '2', true)) { + x = divide(x, 2, 33); + result = add(result, '1'); + } + var fractionalPart = ln(x); + return roundOff(add(result, divide(fractionalPart, LN2,33)), 32); +} + +export function log(base: string | number) { + return roundOff(multiply(ln2(base), LN10), 32) +} + +export function log10(base: string | number) { + return divide(log(base), LN10, 32) +} \ No newline at end of file diff --git a/src/modulus.ts b/src/modulus.ts index cee32cb..dbe4bcc 100644 --- a/src/modulus.ts +++ b/src/modulus.ts @@ -5,7 +5,7 @@ import { negate, subtract } from './subtract'; import { RoundingModes } from './roundingModes'; import { abs } from './abs'; -export function modulusE(n: number | string, base: number | string = 1, percision: number | undefined = undefined) { +export function modulusE(n: number | string, base: number | string = 1, precision: number | undefined = undefined) { if (base == 0) { throw new Error('Cannot divide by 0'); @@ -16,10 +16,10 @@ export function modulusE(n: number | string, base: number | string = 1, percisio validate(base); - return subtract(n, multiply(base, roundOff(divide(n, base, percision), 0, RoundingModes.FLOOR))); + return subtract(n, multiply(base, roundOff(divide(n, base, precision), 0, RoundingModes.FLOOR))); } -export function modulus(dividend: number | string, divisor: number | string = 1, percision: number | undefined = undefined) { +export function modulus(dividend: number | string, divisor: number | string = 1, precision: number | undefined = undefined) { if (divisor == 0) { throw new Error('Cannot divide by 0'); } @@ -29,7 +29,7 @@ export function modulus(dividend: number | string, divisor: number | string = 1, validate(divisor); - const result = modulusE(abs(dividend), abs(divisor), percision); + const result = modulusE(abs(dividend), abs(divisor), precision); return (dividend.includes('-')) ? negate(result) : result; } diff --git a/src/pow.ts b/src/pow.ts index a7d3ab0..46cb149 100644 --- a/src/pow.ts +++ b/src/pow.ts @@ -1,5 +1,5 @@ import { abs } from "./abs"; -import { compareTo, greaterThan, isExatclyOne, isExatclyZero, lessThan } from "./compareTo"; +import { compareTo, equals, greaterThan, isExatclyOne, isExatclyZero, lessThan } from "./compareTo"; import { divide } from "./divide"; import { modulus } from "./modulus"; import { multiply } from "./multiply"; @@ -8,8 +8,7 @@ import { RoundingModes } from "./roundingModes"; import { stripTrailingZero } from "./stripTrailingZero"; import { negate as negateFn, subtract } from "./subtract"; import { add } from "./add"; -import { Euler, tolerance } from "./utils"; - +import { tolerance } from "./utils"; /** * Calculates the power of a given base raised to an integer exponent @@ -50,20 +49,20 @@ import { Euler, tolerance } from "./utils"; * ``` */ -export function pow(base: number | string, exponent: number | string, percision: number | undefined = undefined, negate: boolean | undefined = false):string { +export function pow(base: number | string, exponent: number | string, precision: number | undefined = undefined, negate: boolean | undefined = false): string { exponent = exponent.toString(); base = base.toString(); - if(isExatclyZero(exponent)){ + if (isExatclyZero(exponent)) { return '1' } - if(!exponent.includes('-') && isExatclyOne(exponent)){ + if (!exponent.includes('-') && isExatclyOne(exponent)) { return base } - if(isExatclyZero(base) && exponent.includes('-') && isExatclyOne(exponent)){ + if (isExatclyZero(base) && exponent.includes('-') && isExatclyOne(exponent)) { throw Error('0^(-1) is undefined'); } @@ -72,8 +71,8 @@ export function pow(base: number | string, exponent: number | string, percision: const negativeBase = base.includes('-'); const isBase10 = compareTo(abs(base), '10') == 0; const negativeBase10 = isBase10 && negativeBase; - const orderOrPercision = reciprical && compareTo(abs(exponent), '1') == -1 ? percision : Number(abs(exponent)); - const recipricalPercision = isBase10 ? orderOrPercision : percision; + const orderOrprecision = reciprical && compareTo(abs(exponent), '1') == -1 ? precision : Number(abs(exponent)); + const recipricalprecision = isBase10 ? orderOrprecision : precision; let fractionalExponent = '1'; let result = '1'; @@ -85,7 +84,7 @@ export function pow(base: number | string, exponent: number | string, percision: if (!isExatclyZero(remainder)) { - if(negativeBase && !negativeBase10){ + if (negativeBase && !negativeBase10) { negate = !negate } @@ -114,12 +113,12 @@ export function pow(base: number | string, exponent: number | string, percision: } result = multiply(result, fractionalExponent); - result = (percision) ? roundOff(result, percision) : result; - result = (reciprical) ? divide(1, result, recipricalPercision) : result; + result = (precision) ? roundOff(result, precision) : result; + result = (reciprical) ? divide(1, result, recipricalprecision) : result; return (negate) ? stripTrailingZero(negateFn(result)) : stripTrailingZero(result); }; -export function nthRoot(x: number | string, n: number | string, percision = 8) { +export function nthRoot(x: number | string, n: number | string, precision = 8) { x = x.toString(); n = n.toString(); @@ -128,15 +127,15 @@ export function nthRoot(x: number | string, n: number | string, percision = 8) { let guess = '1'; let nMinusOne = subtract(n, 1); - let percisionMax = Number(multiply(percision + 1, 2)); + let precisionMax = Number(multiply(precision + 1, 2)); let i = 0; - while (i < percisionMax) { + while (i < precisionMax) { - let newGuess = divide(add(stripTrailingZero(divide(x, pow(guess, nMinusOne), percisionMax)), multiply(guess, nMinusOne)), n, percisionMax); + let newGuess = divide(add(stripTrailingZero(divide(x, pow(guess, nMinusOne), precisionMax)), multiply(guess, nMinusOne)), n, precisionMax); - if (lessThan(newGuess, tolerance(percision))) { - return stripTrailingZero(roundOff(newGuess, percision + 1)) + if (lessThan(newGuess, tolerance(precision))) { + return stripTrailingZero(roundOff(newGuess, precision + 1)) } guess = stripTrailingZero(newGuess); @@ -144,40 +143,38 @@ export function nthRoot(x: number | string, n: number | string, percision = 8) { i++; } - return stripTrailingZero(roundOff(guess, percision + 1)) + return stripTrailingZero(roundOff(guess, precision + 1)) } -export function sqRoot(base: string|number, percision = 32) { - percision = Math.max(percision, 32); - return nthRoot(base, 2, percision); +export function sqRoot(base: string | number, precision = 32) { + precision = Math.max(precision, 32); + return nthRoot(base, 2, precision); } -export function cbRoot(base: string|number, percision = 32) { - percision = Math.max(percision, 32); - return nthRoot(base, 3, percision); +export function cbRoot(base: string | number, precision = 32) { + precision = Math.max(precision, 32); + return nthRoot(base, 3, precision); } -export function root4(base: string|number, percision = 32) { - percision = Math.max(percision, 32); - return sqRoot(sqRoot(base, percision), percision); +export function root4(base: string | number, precision = 32) { + precision = Math.max(precision, 32); + return sqRoot(sqRoot(base, precision), precision); } -export function root5(base: string|number, percision = 32) { - percision = Math.max(percision, 32); - return nthRoot(base, 5, percision); +export function root5(base: string | number, precision = 32) { + precision = Math.max(precision, 32); + return nthRoot(base, 5, precision); } -export function root10(base: string|number, percision = 32) { - percision = Math.max(percision, 32); - return sqRoot(root5(base, percision), percision); +export function root10(base: string | number, precision = 32) { + precision = Math.max(precision, 32); + return sqRoot(root5(base, precision), precision); } -export function exp(exponent: number | string){ - return pow(Euler(32),exponent) -} - function validate(oparand: string) { if (oparand.includes('.')) { throw Error('Root base of non-integers not supported'); } } + + diff --git a/src/trig.spec.ts b/src/trig.spec.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/trig.ts b/src/trig.ts new file mode 100644 index 0000000..dfccc47 --- /dev/null +++ b/src/trig.ts @@ -0,0 +1,50 @@ +import { add } from "./add"; +import { divide } from "./divide"; +import { exp } from "./logarithm"; +import { multiply } from "./multiply"; +import { pow } from "./pow"; +import { roundOff } from "./round"; +import { stripTrailingZero } from "./stripTrailingZero"; +import { negate, subtract } from "./subtract"; +import { alternatingSeries, factorial, isAproxOne, isAproxZero, sign } from "./utils"; + +// PI up to the first 64 decimal places +export const PI = '3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229' + +export function sin(x: number | string){ + x = x.toString(); + let s = roundOff(alternatingSeries(1, 32, (n: number)=>{ + const N = (n * 2) - 1; + return divide(pow(x, N, 32), factorial(N), 33); + }), 32); + return isAproxZero(s)? '0': isAproxOne(s)? multiply('1', sign(s)): s; +} + +export function sinh(x: number | string){ + x = x.toString(); + return stripTrailingZero(multiply('0.5', subtract(exp(x), exp(negate(x))))); +} + +export function cos(x: number | string){ + x = x.toString(); + let s = subtract('1',roundOff(alternatingSeries(1, 32, (n: number)=>{ + const N = (n * 2); + return divide(pow(x, N), factorial(N), 33); + }), 32)); + return isAproxOne(s)? multiply('1', sign(s)): isAproxZero(s)? '0': s; +} + +export function cosh(x: number | string){ + x = x.toString(); + return stripTrailingZero(multiply('0.5', add(exp(x), exp(negate(x))))); +} + +export function tan(x: number | string){ + x = x.toString(); + return stripTrailingZero(divide(sin(x), cos(x), 32)); +} + +export function tanh(x: number | string){ + x = x.toString(); + return stripTrailingZero(divide(sinh(x), cosh(x), 32)); +} \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts index 120b086..8ae65c4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,10 +1,11 @@ import { abs } from "./abs"; import { add } from "./add"; -import { compareTo, isExatclyOne, isExatclyZero, lessThan } from "./compareTo"; +import { compareTo, equals, greaterThan, isExatclyOne, isExatclyZero, lessThan } from "./compareTo"; import { divide } from "./divide"; import { multiply } from "./multiply"; +import { sqRoot } from "./pow"; import { roundOff } from "./round"; -import { subtract } from "./subtract"; +import { negate, subtract } from "./subtract"; export const factorial = (n: number | string): string => { @@ -34,7 +35,7 @@ export const factorial = (n: number | string): string => { } -function sigma(n: number | string, limit: number | string, fn: (n:number|string, ...args) => any, ...args:any): string { +export function sigma(n: number | string, limit: number | string, fn: (n:number|string, ...args) => any, ...args:any): string { n = n.toString(); limit = limit.toString(); @@ -51,50 +52,78 @@ function sigma(n: number | string, limit: number | string, fn: (n:number|string, return result; } - let next = subtract(limit,'1'); result = add(result, fn(limit, ...args)); - limit = next; + limit = subtract(limit,'1'); } return result } -export function tolerance(percision: number | string){ - percision = percision.toString(); - validateInteger(percision); - if(percision == '0') return '0'; - if(percision.startsWith('-')) return `1${new Array(Number(-percision)).join('0')}`; - return `0.${new Array(Number(percision) - 1).join('0')}1` +export function alternatingSeries(n: number | string, limit: number | string, fn: (n:number|string) => any, _sign: number | string = '1'): string { + + n = n.toString(); + limit = limit.toString(); + _sign = sign(_sign).toString(); + + validateInteger(n); + validateInteger(limit); + validatePositive(n); + validatePositive(limit); + + let result = '0'; + while(true){ + + const next = multiply(_sign, fn(n)) + + if(lessThan(abs(next), tolerance(limit))){ + return result; + } + + result = add(result, next); + _sign = negate(_sign) + n = add(n,'1'); + } + + return result + } -export function Euler(percision: number = 32) { - percision = Math.max(16, percision) - return roundOff(sigma(0, percision, (n: string | number)=>{ - return divide('1', factorial(n), percision + 1) - }), percision); +export function tolerance(precision: number | string){ + precision = precision.toString(); + validateInteger(precision); + if(precision == '0') return '0'; + if(precision.startsWith('-')) return `1${new Array(Number(-precision)).join('0')}`; + return `0.${new Array(Number(precision) - 1).join('0')}1` } -export function isAproxZero(number: string | number, percision: number = 8) { - percision = Math.max(1, percision) +export function isAproxZero(number: string | number, precision: number = 8) { + precision = Math.max(1, precision) number = abs(number.toString()); if(isExatclyZero(number)) return true; - if(lessThan(number, tolerance(percision), true)) return true; + if(lessThan(number, tolerance(precision), true)) return true; return false; } -export function isAproxOne(number: string | number, percision: number = 8) { - percision = Math.max(1, percision) +export function isAproxOne(number: string | number, precision: number = 8) { + precision = Math.max(1, precision) number = abs(number.toString()); if(isExatclyOne(number)) return true; - if(lessThan(abs(subtract('1', number)), tolerance(percision), true)) return true; + if(lessThan(abs(subtract('1', number)), tolerance(precision), true)) return true; return false; } +export function sign(number: string | number){ + number = number.toString(); + if(isExatclyZero(number)) return 0; + if(number.includes('-')) return -1; + return 1; +} + function validateInteger(number: string) { if (number.includes('.')) { throw new Error('Non-integers not supported');