diff --git a/src/modulus.spec.ts b/src/modulus.spec.ts index b07734f..3004979 100644 --- a/src/modulus.spec.ts +++ b/src/modulus.spec.ts @@ -1,6 +1,6 @@ -import { modulus } from "./modulus"; +import { modulus, modulusE } from "./modulus"; -describe("modulus", function () { +describe("modulus(remainder)", function () { it("should modulus(7,4) = 3", function () { expect(modulus(7, 4)).toBe("3"); }); @@ -10,7 +10,7 @@ describe("modulus", function () { it("should modulus(7,-4) = 3", function () { expect(modulus(7, -4)).toBe("3"); }); - it("should modulus(-7,-4) = 3", function () { + it("should modulus(-7,-4) = -3", function () { expect(modulus(-7, -4)).toBe("-3"); }); it("should modulus(-7,0) throw", function () { @@ -31,3 +31,35 @@ describe("modulus", function () { expect(() => modulus("7.5", "32")).toThrowError(); }); }); + +describe("modulus(Euclidean division)", function () { + it("should modulusE(7,4) = 3", function () { + expect(modulusE(7, 4)).toBe("3"); + }); + it("should modulusE(-7,4) = 1", function () { + expect(modulusE(-7, 4)).toBe("1"); + }); + it("should modulus(7,-4) = -1", function () { + expect(modulusE(7, -4)).toBe("-1"); + }); + it("should modulus(-7,-4) = -3", function () { + expect(modulusE(-7, -4)).toBe("-3"); + }); + it("should modulus(-7,0) throw", function () { + expect(() => modulusE(-7, 0)).toThrowError(); + }); + + it("should modulusE(76457896543456, 77732) = 45352", function () { + expect(modulusE("76457896543456", "77732")).toBe("45352"); + }); + + it("should modulusE(7.5, 3.2) to throw error", function () { + expect(() => modulusE("7.5", "3.2")).toThrowError(); + }); + it("should modulusE(75, 3.2) to throw error", function () { + expect(() => modulusE("75", "3.2")).toThrowError(); + }); + it("should modulusE(7.5, 32) to throw error", function () { + expect(() => modulusE("7.5", "32")).toThrowError(); + }); +}); diff --git a/src/modulus.ts b/src/modulus.ts index 1e3464b..41b9935 100644 --- a/src/modulus.ts +++ b/src/modulus.ts @@ -1,10 +1,71 @@ import { divide } from './divide'; import { roundOff } from './round'; import { multiply } from './multiply'; -import { subtract } from './subtract'; +import { negate, subtract } from './subtract'; import { RoundingModes } from './roundingModes'; +import { abs } from './abs'; -export function modulus(dividend: number|string, divisor:number|string) { + +// export function modulus(dividend: number | string, divisor: number | string) { +// if (divisor == 0) { +// throw new Error('Cannot divide by 0'); +// } + +// dividend = dividend.toString(); +// divisor = divisor.toString(); + +// validate(dividend); +// validate(divisor); + +// let sign = ''; +// if (dividend[0] == '-') { +// sign = '-'; +// dividend = dividend.substr(1); +// } +// if (divisor[0] == '-') { +// divisor = divisor.substr(1); +// } + +// let result = subtract(dividend, multiply(divisor, roundOff(divide(dividend, divisor), 0, RoundingModes.FLOOR))); +// return sign + result; +// } + +// function validate(oparand: string) { +// if (oparand.indexOf('.') != -1) { // oparand.includes('.') could also work here +// throw new Error('Modulus of non-integers not supported'); +// } +// } + + +// For technical purposes, this is actually Remainder, and not Modulus (Euclidean division). +// Could seperate the Modulus equation into its own function, +// then use it within the Remainder function after proper negation. +// Proper neation only depends on the sign of the dividend, where the result takes the sign +// of the divident, and ignores the sign of the divisor. For this effect, the absolute values of +// each oparand is used, then the original sign of the divident dictates +// nagation of the result to negative or not. + + +// To ensure backwards compatibility, the new Modulus function could be named 'modulusE', +// where 'E' denotes 'Euclidean' in 'Euclidean division'. + +// Sugested changes are bellow + +export function modulusE(n: number | string, base: number | string = '1', percision: number | undefined = undefined) { + if (base == 0) { + throw new Error('Cannot divide by 0'); + } + + n = n.toString(); + base = base.toString(); + + validate(n); + validate(base); + + return subtract(n, multiply(base, roundOff(divide(n, base, percision), 0, RoundingModes.FLOOR))); +} + +export function modulus(dividend: number | string, divisor: number | string, percision: number | undefined = undefined) { if (divisor == 0) { throw new Error('Cannot divide by 0'); } @@ -15,21 +76,19 @@ export function modulus(dividend: number|string, divisor:number|string) { validate(dividend); validate(divisor); - let sign = ''; - if(dividend[0] == '-'){ - sign = '-'; - dividend = dividend.substr(1); - } - if(divisor[0] == '-'){ - divisor = divisor.substr(1); + let sign = false; + if (dividend[0] == '-') { // or dividend.includes('-') + sign = true; } - let result = subtract(dividend, multiply(divisor, roundOff(divide(dividend, divisor), 0, RoundingModes.FLOOR))); - return sign+result; + const result = modulusE(abs(dividend), abs(divisor), percision); + return (sign) ? negate(result) : result; } function validate(oparand: string) { - if (oparand.indexOf('.') != -1) { + if (oparand.indexOf('.') != -1) { // or oparand.includes('.') throw new Error('Modulus of non-integers not supported'); } } + + diff --git a/src/pow.spec.ts b/src/pow.spec.ts new file mode 100644 index 0000000..bab4937 --- /dev/null +++ b/src/pow.spec.ts @@ -0,0 +1,53 @@ +import { NonIntegerExponentError, intPow } from "./pow"; + +describe("intPow", function () { + + it("should be defined", function () { + expect(intPow).toBeDefined(); + }); + + it("should: 2^2 = 4", function () { + expect(intPow(2, 2)).toBe("4"); + }); + + it("should: -2^2 = 4", function () { + expect(intPow(-2, 2)).toBe("4"); + }); + + it("should: -2^3 = -8", function () { + expect(intPow(-2, 3)).toBe("-8"); + }); + + describe('Negated', function () { + + it("should: -(2^2) = -4", function () { + expect(intPow(2, 2, true)).toBe("-4"); + }); + + it("should: -(-2^2) = -4", function () { + expect(intPow(2, 2, true)).toBe("-4"); + }); + + it("should: -(-2^3) = 8", function () { + expect(intPow(-2, 3, true)).toBe("8"); + }); + }) + + describe('Special Cases', function () { + it("should: 2^0 = 1", function () { + expect(intPow(2, 0)).toBe("1"); + }); + + it("should: -2^1 = 2", function () { + expect(intPow(2, 1)).toBe("2"); + }); + }) + + describe('Errors and Exceptions', function () { + it("should throw error", function () { + expect(()=>{intPow(2, 2.5)}).toThrowError(); + }); + + }) + +}); diff --git a/src/pow.ts b/src/pow.ts new file mode 100644 index 0000000..6fc444e --- /dev/null +++ b/src/pow.ts @@ -0,0 +1,168 @@ +import { abs } from "./abs"; +import { compareTo } from "./compareTo"; +import { divide } from "./divide"; +import { modulus } from "./modulus"; +import { multiply } from "./multiply"; +import { roundOff } from "./round"; +import { RoundingModes } from "./roundingModes"; +import { negate as negateFn, subtract } from "./subtract"; + +export type ExponentErrorOrException = { + message: string, + type: 'error' | 'exception', +} + +export const NonIntegerExponentError: ExponentErrorOrException = { + message: `Exponent must be an integer.`, + type: 'error', +} + +export const ComplexExponentException: ExponentErrorOrException = { + message: `Result is a Complex number with only an Imaginary component.`, + type: 'exception', +} + + + + +/** + * Calculates the power of a given base raised to an integer exponent + * + * @param base - Base number + * @param exponent - Exponent integer + * @param negate - If set to true, parameters will be evaluated as `-(x ^ n)` + * + * @returns The resulting power as a string + * + * @throws {NonIntegerExponentError} - If `exponent` is a non-integer number, this error is thrown. + * + * @example Basic usage: + * ``` + * // Positive Base + * console.log(pow(2,2)) // Prints '4' + * // Negative Base + * console.log(pow(-2,2)) // Prints '4' + * // Negative Base where the result will be a negative number + * console.log(pow(-2,3)) // Prints '-8' + * ``` + * + * @example Negation usage: + * ``` + * // Positive Base + * console.log(pow(2, 2, true)) // Prints '-4' + * // Negative Base + * console.log(pow(-2, 2, true)) // Prints '-4' + * // Negative Base where the result will be a negative number + * console.log(pow(-2, 3, true)) // Prints '8' + * ``` + * + * @example Special cases: + * ``` + * // Exponent of 0 + * console.log(pow(2, 0)) // Prints '1' + * // Exponent of 1 + * console.log(pow(2, 1)) // Prints '2' + * ``` + */ + +// Integer Exponent Only Implementation + +export function intPow(base: number | string, exponent: number | string, negate: boolean = false) { + + exponent = exponent.toString(); + base = base.toString(); + + try { + if (exponent.includes('.')) { + throw NonIntegerExponentError + } + + // Special Handling of Complex numbers + + // const imaginary = exponent < 0 && Number(remainder) > 0 && Number(remainder) < 1; + + // if (imaginary) { + // throw ComplexExponentException + // } + + } catch (errorOrException) { + errorOrException = errorOrException + switch (errorOrException.type) { + case 'error': + const error = Error(`${errorOrException.message}`) + console.error(error) + throw error + // case 'exception': // For Complex nunmbers + // console.error(`Exception(${errorOrException.severity}): ${errorOrException.message}`) + // return NaN // Todo: Break or continue + } + } + + const reciprical = compareTo(exponent, '0') == -1; + const base10Percision = compareTo(base, '10') == 0 ? exponent.length : undefined; + + let result = '1'; + + exponent = abs(exponent) + + while (compareTo(exponent, '0') == 1) { + if (modulus(exponent, 2) == '1') { result = multiply(result, base) } + base = multiply(base, base); + exponent = roundOff(divide(exponent, 2), 0, RoundingModes.FLOOR); + } + + result = (reciprical) ? divide(1, result, base10Percision) : result; + return (negate) ? negateFn(result) : result; +}; + +// Todo: Core Powers function +// Needs Nth-Root implementation for fractional powers + +// export function pow(x: number, n: number, negate: boolean = false) { + +// const reciprical = n < 0; +// const percision = x == 10 && n >= 1 ? Math.abs(n) : undefined + +// const exp = abs(n); +// const floor = roundOff(exp, 0, RoundingModes.FLOOR); +// const remainder = subtract(exp, floor); +// const imaginary = x < 0 && Number(remainder) > 0 && Number(remainder) < 1; + +// try { +// if (imaginary) { +// x = Math.abs(x); +// negate = true; +// throw `Complex Number Exception: Cannot calculate powers resulting in Imaginary Numbers. Base will be subsituted with it's absolute value, and result will be negated.`; +// } +// } catch (warning) { +// console.warn(warning); +// } + +// const base = x; + +// let result = x.toString(); + +// if (Number(remainder) > 0 && Number(remainder) < 1) { +// const factor = divide(1, remainder, 3); +// const root = nthRoot(x, Number(factor)); + +// if (Number(floor) > 0) { +// for (let i = 0; i < Number(floor) - 1; i++) { +// result = multiply(result, base); +// } +// } else { +// result = '1'; +// } + +// result = multiply(result, root); +// } else if (n == 0) { +// result = '1'; +// } else { +// for (let i = 0; i < Number(exp) - 1; i++) { +// result = multiply(result, base); +// } +// } +// result = negate ? negateFn(result) : result; +// result = reciprical ? divide(1, result, percision) : result; +// return result; +// }; \ No newline at end of file