Skip to content

Commit 2f32e0a

Browse files
authored
Implementing Powers (#127)
* Integer only exponents * Removed bitwise operations - Allows for any size Integer to be used as an exponent * Suggested enhancement Adds 'Euclidean division' Modulus operation and simplifies Modulus Remainder sign logic. Unit tests added.
1 parent 5a47b16 commit 2f32e0a

File tree

4 files changed

+327
-15
lines changed

4 files changed

+327
-15
lines changed

src/modulus.spec.ts

+35-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { modulus } from "./modulus";
1+
import { modulus, modulusE } from "./modulus";
22

3-
describe("modulus", function () {
3+
describe("modulus(remainder)", function () {
44
it("should modulus(7,4) = 3", function () {
55
expect(modulus(7, 4)).toBe("3");
66
});
@@ -10,7 +10,7 @@ describe("modulus", function () {
1010
it("should modulus(7,-4) = 3", function () {
1111
expect(modulus(7, -4)).toBe("3");
1212
});
13-
it("should modulus(-7,-4) = 3", function () {
13+
it("should modulus(-7,-4) = -3", function () {
1414
expect(modulus(-7, -4)).toBe("-3");
1515
});
1616
it("should modulus(-7,0) throw", function () {
@@ -31,3 +31,35 @@ describe("modulus", function () {
3131
expect(() => modulus("7.5", "32")).toThrowError();
3232
});
3333
});
34+
35+
describe("modulus(Euclidean division)", function () {
36+
it("should modulusE(7,4) = 3", function () {
37+
expect(modulusE(7, 4)).toBe("3");
38+
});
39+
it("should modulusE(-7,4) = 1", function () {
40+
expect(modulusE(-7, 4)).toBe("1");
41+
});
42+
it("should modulus(7,-4) = -1", function () {
43+
expect(modulusE(7, -4)).toBe("-1");
44+
});
45+
it("should modulus(-7,-4) = -3", function () {
46+
expect(modulusE(-7, -4)).toBe("-3");
47+
});
48+
it("should modulus(-7,0) throw", function () {
49+
expect(() => modulusE(-7, 0)).toThrowError();
50+
});
51+
52+
it("should modulusE(76457896543456, 77732) = 45352", function () {
53+
expect(modulusE("76457896543456", "77732")).toBe("45352");
54+
});
55+
56+
it("should modulusE(7.5, 3.2) to throw error", function () {
57+
expect(() => modulusE("7.5", "3.2")).toThrowError();
58+
});
59+
it("should modulusE(75, 3.2) to throw error", function () {
60+
expect(() => modulusE("75", "3.2")).toThrowError();
61+
});
62+
it("should modulusE(7.5, 32) to throw error", function () {
63+
expect(() => modulusE("7.5", "32")).toThrowError();
64+
});
65+
});

src/modulus.ts

+71-12
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,71 @@
11
import { divide } from './divide';
22
import { roundOff } from './round';
33
import { multiply } from './multiply';
4-
import { subtract } from './subtract';
4+
import { negate, subtract } from './subtract';
55
import { RoundingModes } from './roundingModes';
6+
import { abs } from './abs';
67

7-
export function modulus(dividend: number|string, divisor:number|string) {
8+
9+
// export function modulus(dividend: number | string, divisor: number | string) {
10+
// if (divisor == 0) {
11+
// throw new Error('Cannot divide by 0');
12+
// }
13+
14+
// dividend = dividend.toString();
15+
// divisor = divisor.toString();
16+
17+
// validate(dividend);
18+
// validate(divisor);
19+
20+
// let sign = '';
21+
// if (dividend[0] == '-') {
22+
// sign = '-';
23+
// dividend = dividend.substr(1);
24+
// }
25+
// if (divisor[0] == '-') {
26+
// divisor = divisor.substr(1);
27+
// }
28+
29+
// let result = subtract(dividend, multiply(divisor, roundOff(divide(dividend, divisor), 0, RoundingModes.FLOOR)));
30+
// return sign + result;
31+
// }
32+
33+
// function validate(oparand: string) {
34+
// if (oparand.indexOf('.') != -1) { // oparand.includes('.') could also work here
35+
// throw new Error('Modulus of non-integers not supported');
36+
// }
37+
// }
38+
39+
40+
// For technical purposes, this is actually Remainder, and not Modulus (Euclidean division).
41+
// Could seperate the Modulus equation into its own function,
42+
// then use it within the Remainder function after proper negation.
43+
// Proper neation only depends on the sign of the dividend, where the result takes the sign
44+
// of the divident, and ignores the sign of the divisor. For this effect, the absolute values of
45+
// each oparand is used, then the original sign of the divident dictates
46+
// nagation of the result to negative or not.
47+
48+
49+
// To ensure backwards compatibility, the new Modulus function could be named 'modulusE',
50+
// where 'E' denotes 'Euclidean' in 'Euclidean division'.
51+
52+
// Sugested changes are bellow
53+
54+
export function modulusE(n: number | string, base: number | string = '1', percision: number | undefined = undefined) {
55+
if (base == 0) {
56+
throw new Error('Cannot divide by 0');
57+
}
58+
59+
n = n.toString();
60+
base = base.toString();
61+
62+
validate(n);
63+
validate(base);
64+
65+
return subtract(n, multiply(base, roundOff(divide(n, base, percision), 0, RoundingModes.FLOOR)));
66+
}
67+
68+
export function modulus(dividend: number | string, divisor: number | string, percision: number | undefined = undefined) {
869
if (divisor == 0) {
970
throw new Error('Cannot divide by 0');
1071
}
@@ -15,21 +76,19 @@ export function modulus(dividend: number|string, divisor:number|string) {
1576
validate(dividend);
1677
validate(divisor);
1778

18-
let sign = '';
19-
if(dividend[0] == '-'){
20-
sign = '-';
21-
dividend = dividend.substr(1);
22-
}
23-
if(divisor[0] == '-'){
24-
divisor = divisor.substr(1);
79+
let sign = false;
80+
if (dividend[0] == '-') { // or dividend.includes('-')
81+
sign = true;
2582
}
2683

27-
let result = subtract(dividend, multiply(divisor, roundOff(divide(dividend, divisor), 0, RoundingModes.FLOOR)));
28-
return sign+result;
84+
const result = modulusE(abs(dividend), abs(divisor), percision);
85+
return (sign) ? negate(result) : result;
2986
}
3087

3188
function validate(oparand: string) {
32-
if (oparand.indexOf('.') != -1) {
89+
if (oparand.indexOf('.') != -1) { // or oparand.includes('.')
3390
throw new Error('Modulus of non-integers not supported');
3491
}
3592
}
93+
94+

src/pow.spec.ts

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { NonIntegerExponentError, intPow } from "./pow";
2+
3+
describe("intPow", function () {
4+
5+
it("should be defined", function () {
6+
expect(intPow).toBeDefined();
7+
});
8+
9+
it("should: 2^2 = 4", function () {
10+
expect(intPow(2, 2)).toBe("4");
11+
});
12+
13+
it("should: -2^2 = 4", function () {
14+
expect(intPow(-2, 2)).toBe("4");
15+
});
16+
17+
it("should: -2^3 = -8", function () {
18+
expect(intPow(-2, 3)).toBe("-8");
19+
});
20+
21+
describe('Negated', function () {
22+
23+
it("should: -(2^2) = -4", function () {
24+
expect(intPow(2, 2, true)).toBe("-4");
25+
});
26+
27+
it("should: -(-2^2) = -4", function () {
28+
expect(intPow(2, 2, true)).toBe("-4");
29+
});
30+
31+
it("should: -(-2^3) = 8", function () {
32+
expect(intPow(-2, 3, true)).toBe("8");
33+
});
34+
})
35+
36+
describe('Special Cases', function () {
37+
it("should: 2^0 = 1", function () {
38+
expect(intPow(2, 0)).toBe("1");
39+
});
40+
41+
it("should: -2^1 = 2", function () {
42+
expect(intPow(2, 1)).toBe("2");
43+
});
44+
})
45+
46+
describe('Errors and Exceptions', function () {
47+
it("should throw error", function () {
48+
expect(()=>{intPow(2, 2.5)}).toThrowError();
49+
});
50+
51+
})
52+
53+
});

src/pow.ts

+168
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import { abs } from "./abs";
2+
import { compareTo } from "./compareTo";
3+
import { divide } from "./divide";
4+
import { modulus } from "./modulus";
5+
import { multiply } from "./multiply";
6+
import { roundOff } from "./round";
7+
import { RoundingModes } from "./roundingModes";
8+
import { negate as negateFn, subtract } from "./subtract";
9+
10+
export type ExponentErrorOrException = {
11+
message: string,
12+
type: 'error' | 'exception',
13+
}
14+
15+
export const NonIntegerExponentError: ExponentErrorOrException = {
16+
message: `Exponent must be an integer.`,
17+
type: 'error',
18+
}
19+
20+
export const ComplexExponentException: ExponentErrorOrException = {
21+
message: `Result is a Complex number with only an Imaginary component.`,
22+
type: 'exception',
23+
}
24+
25+
26+
27+
28+
/**
29+
* Calculates the power of a given base raised to an integer exponent
30+
*
31+
* @param base - Base number
32+
* @param exponent - Exponent integer
33+
* @param negate - If set to true, parameters will be evaluated as `-(x ^ n)`
34+
*
35+
* @returns The resulting power as a string
36+
*
37+
* @throws {NonIntegerExponentError} - If `exponent` is a non-integer number, this error is thrown.
38+
*
39+
* @example Basic usage:
40+
* ```
41+
* // Positive Base
42+
* console.log(pow(2,2)) // Prints '4'
43+
* // Negative Base
44+
* console.log(pow(-2,2)) // Prints '4'
45+
* // Negative Base where the result will be a negative number
46+
* console.log(pow(-2,3)) // Prints '-8'
47+
* ```
48+
*
49+
* @example Negation usage:
50+
* ```
51+
* // Positive Base
52+
* console.log(pow(2, 2, true)) // Prints '-4'
53+
* // Negative Base
54+
* console.log(pow(-2, 2, true)) // Prints '-4'
55+
* // Negative Base where the result will be a negative number
56+
* console.log(pow(-2, 3, true)) // Prints '8'
57+
* ```
58+
*
59+
* @example Special cases:
60+
* ```
61+
* // Exponent of 0
62+
* console.log(pow(2, 0)) // Prints '1'
63+
* // Exponent of 1
64+
* console.log(pow(2, 1)) // Prints '2'
65+
* ```
66+
*/
67+
68+
// Integer Exponent Only Implementation
69+
70+
export function intPow(base: number | string, exponent: number | string, negate: boolean = false) {
71+
72+
exponent = exponent.toString();
73+
base = base.toString();
74+
75+
try {
76+
if (exponent.includes('.')) {
77+
throw NonIntegerExponentError
78+
}
79+
80+
// Special Handling of Complex numbers
81+
82+
// const imaginary = exponent < 0 && Number(remainder) > 0 && Number(remainder) < 1;
83+
84+
// if (imaginary) {
85+
// throw ComplexExponentException
86+
// }
87+
88+
} catch (errorOrException) {
89+
errorOrException = <ExponentErrorOrException>errorOrException
90+
switch (errorOrException.type) {
91+
case 'error':
92+
const error = Error(`${errorOrException.message}`)
93+
console.error(error)
94+
throw error
95+
// case 'exception': // For Complex nunmbers
96+
// console.error(`Exception(${errorOrException.severity}): ${errorOrException.message}`)
97+
// return NaN // Todo: Break or continue
98+
}
99+
}
100+
101+
const reciprical = compareTo(exponent, '0') == -1;
102+
const base10Percision = compareTo(base, '10') == 0 ? exponent.length : undefined;
103+
104+
let result = '1';
105+
106+
exponent = abs(exponent)
107+
108+
while (compareTo(exponent, '0') == 1) {
109+
if (modulus(exponent, 2) == '1') { result = multiply(result, base) }
110+
base = multiply(base, base);
111+
exponent = roundOff(divide(exponent, 2), 0, RoundingModes.FLOOR);
112+
}
113+
114+
result = (reciprical) ? divide(1, result, base10Percision) : result;
115+
return (negate) ? negateFn(result) : result;
116+
};
117+
118+
// Todo: Core Powers function
119+
// Needs Nth-Root implementation for fractional powers
120+
121+
// export function pow(x: number, n: number, negate: boolean = false) {
122+
123+
// const reciprical = n < 0;
124+
// const percision = x == 10 && n >= 1 ? Math.abs(n) : undefined
125+
126+
// const exp = abs(n);
127+
// const floor = roundOff(exp, 0, RoundingModes.FLOOR);
128+
// const remainder = subtract(exp, floor);
129+
// const imaginary = x < 0 && Number(remainder) > 0 && Number(remainder) < 1;
130+
131+
// try {
132+
// if (imaginary) {
133+
// x = Math.abs(x);
134+
// negate = true;
135+
// 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.`;
136+
// }
137+
// } catch (warning) {
138+
// console.warn(warning);
139+
// }
140+
141+
// const base = x;
142+
143+
// let result = x.toString();
144+
145+
// if (Number(remainder) > 0 && Number(remainder) < 1) {
146+
// const factor = divide(1, remainder, 3);
147+
// const root = nthRoot(x, Number(factor));
148+
149+
// if (Number(floor) > 0) {
150+
// for (let i = 0; i < Number(floor) - 1; i++) {
151+
// result = multiply(result, base);
152+
// }
153+
// } else {
154+
// result = '1';
155+
// }
156+
157+
// result = multiply(result, root);
158+
// } else if (n == 0) {
159+
// result = '1';
160+
// } else {
161+
// for (let i = 0; i < Number(exp) - 1; i++) {
162+
// result = multiply(result, base);
163+
// }
164+
// }
165+
// result = negate ? negateFn(result) : result;
166+
// result = reciprical ? divide(1, result, percision) : result;
167+
// return result;
168+
// };

0 commit comments

Comments
 (0)