Skip to content

Commit

Permalink
FEATURE : Add support for complex numbers (Close #2)
Browse files Browse the repository at this point in the history
  • Loading branch information
gissehel committed Jun 6, 2022
1 parent 92a6937 commit 5d90af4
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 35 deletions.
1 change: 1 addition & 0 deletions doc/releases/v0.1.34.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* FEATURE : Add support for complex numbers (Close #2)
66 changes: 60 additions & 6 deletions src/slices/rplSlice/commands.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import exportOnWindow from "../../tools/exportOnWindow"
import { createFraction, createList, createNumber, createVar, dupObject } from "./objects"
import { LIST, NUMBER, VAR, COMMAND, PROGRAM, VARCALL, IFTHENELSEEND, STRING, FRACTION } from "./objectTypes"
import { createComplex, createFraction, createList, createNumber, createVar, dupObject } from "./objects"
import { LIST, NUMBER, VAR, COMMAND, PROGRAM, VARCALL, IFTHENELSEEND, STRING, FRACTION, COMPLEX } from "./objectTypes"
import { add, divide, inv, mult, neg, sub } from "./operators"
import { getSign } from "./utils"

export const setError = (state, text, keepInput) => {
state.error = text
Expand Down Expand Up @@ -34,7 +34,8 @@ export const requireStackTypes = (state, n, types) => {
return true
}

const scalars = [NUMBER, STRING, FRACTION]
const scalars = [NUMBER, STRING, FRACTION, COMPLEX]
const realScalars = [NUMBER, FRACTION]

export const requireStackScalars = (state, n) => {
if (!requireStack(state, n)) {
Expand All @@ -50,6 +51,20 @@ export const requireStackScalars = (state, n) => {
return true
}

export const requireStackRealScalars = (state, n) => {
if (!requireStack(state, n)) {
return false
}
const { length } = state.stack
for (let index = 0; index < n; index++) {
if (!realScalars.includes(state.stack[length - n + index].type)) {
setError(state, `Bad argument type; element [${n - index}] should of real scalar type`)
return false
}
}
return true
}

export const cleanErrorState = (state) => {
if (state.error !== null) {
setError(state, null)
Expand Down Expand Up @@ -150,6 +165,22 @@ const require2OperationScalars = (state, code) => {
}
}

const require2OperationRealScalars = (state, code) => {
cleanErrorState(state)
if (requireStackRealScalars(state, 2)) {
const { stack } = state

const [object1, object2] = popNStack(stack, 2)
try {
code(stack, object1, object2)
} catch (e) {
pushStackObjects(stack, [object1, object2])
setError(state, e.message, true)
}

}
}


const require2OperationKeep = (state, code) => {
cleanErrorState(state)
Expand Down Expand Up @@ -259,6 +290,14 @@ export const exec = (state, item, { as_input, as_program } = {}) => {
}
}

const unfract = (number) => {
if(number.type === NUMBER) {
return number
} else if (number.type === FRACTION) {
return createNumber(number.element.num.element / number.element.den.element)
}
throw new Error(`Unkown type ${number.type}`)
}

export const commands = {
'+': (state) => binaryScalarOperation(state, (o1, o2) => add(o1, o2)),
Expand Down Expand Up @@ -355,8 +394,23 @@ export const commands = {
'->fract': (state) => require2OperationTypes(state, [NUMBER, NUMBER], (stack, object1, object2) => pushStack(stack, createFraction(object1, object2))),
'fract->': (state) => require1OperationType(state, FRACTION, (stack, object) => { pushStack(stack, object.element.num); pushStack(stack, object.element.den) }),
'fract': (state) => require1OperationType(state, NUMBER, (stack, object) => pushStack(stack, createFraction(object, createNumber(1)))),
'unfract': (state) => require1OperationType(state, FRACTION, (stack, object) => pushStack(stack, unfract(object))),
'->complex': (state) => require2OperationRealScalars(state, (stack, object1, object2) => pushStack(stack, createComplex(object1, object2))),
'complex->': (state) => require1OperationType(state, COMPLEX, (stack, object) => { pushStack(stack, object.element.re); pushStack(stack, object.element.im) }),
'conj': (state) => require1OperationType(state, COMPLEX, (stack, object) => { pushStack(stack, createComplex( object.element.re, neg(object.element.im))) }),
're': (state) => require1OperationType(state, COMPLEX, (stack, object) => { pushStack(stack, object.element.re) }),
'im': (state) => require1OperationType(state, COMPLEX, (stack, object) => { pushStack(stack, object.element.im) }),
'uncomplex': (state) => require1OperationType(state, COMPLEX, (stack, object) => {
const { re, im } = object.element
if (getSign(im) === 0) {
pushStack(stack, re)
} else {
pushStack(stack, object)
}

}),
'complexunfract': (state) => require1OperationType(state, COMPLEX, (stack, object) => pushStack(stack, createComplex(unfract(object.element.re), unfract(object.element.im)))),
'pi': (state) => pushStack(state.stack, createNumber(Math.PI)),
'e': (state) => pushStack(state.stack, createNumber(Math.E)),
'i': (state) => pushStack(state.stack, createComplex(createNumber(0), createNumber(1))),
}

exportOnWindow({ createNumber, createFraction })
29 changes: 9 additions & 20 deletions src/slices/rplSlice/objects.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,5 @@
import { COMMAND, KEYWORD, LIST, NUMBER, STRING, VAR, PROGRAM, IFTHENELSEEND, VARCALL, FRACTION } from "./objectTypes"

const compute_pgcd = (a,b) => {
a = Math.floor(Math.abs(a));
b = Math.floor(Math.abs(b));
if (b > a) {
var tmp = a;
a = b;
b = tmp;
}
while (true) {
if (b === 0) return a;
a %= b;
if (a === 0) return b;
b %= a;
}
}
import { COMMAND, KEYWORD, LIST, NUMBER, STRING, VAR, PROGRAM, IFTHENELSEEND, VARCALL, FRACTION, COMPLEX } from "./objectTypes"
import { compute_pgcd, getSign } from "./utils"

export const createNumber = (value) => ({ type: NUMBER, element: value, repr: `${value}` })
export const createString = (value) => ({ type: STRING, element: value, repr: `"${value}"` })
Expand All @@ -29,13 +14,17 @@ export const createIfThenElseEnd = (objects_if, objects_then, objects_else) => (
export const createFraction = (num, den) => {
const pgcd = compute_pgcd(num.element, den.element)
if (pgcd !== 1) {
num = createNumber(num.element/pgcd)
den = createNumber(den.element/pgcd)
num = createNumber(num.element / pgcd)
den = createNumber(den.element / pgcd)
}
if (den.element < 0) {
num = createNumber(-num.element)
den = createNumber(-den.element)
}
return { type: FRACTION, element: { num, den }, repr: `${num.element}/${den.element}` }
return { type: FRACTION, element: { num, den }, repr: `${num.repr}/${den.repr}` }
}
export const createComplex = (re, im) => {
return { type: COMPLEX, element: { re, im }, repr: `${re.repr}${getSign(im) >= 0 ? '+' : ''}${im.repr}i` }
}


87 changes: 78 additions & 9 deletions src/slices/rplSlice/operators.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import exportOnWindow from "../../tools/exportOnWindow";
import { createFraction, createNumber, createString } from "./objects";
import { FRACTION, NUMBER, STRING } from "./objectTypes";
import { createComplex, createFraction, createNumber, createString } from "./objects";
import { COMPLEX, FRACTION, NUMBER, STRING } from "./objectTypes";

const isInteger = (x) => Math.floor(x) === x
const isNumberInteger = (x) => isInteger(x.element)

export const neg = (object) => {
if (object.type === NUMBER) {
Expand All @@ -9,6 +12,9 @@ export const neg = (object) => {
if (object.type === FRACTION) {
return createFraction(neg(object.element.num), object.element.den)
}
if (object.type === COMPLEX) {
return createComplex(neg(object.element.re), neg(object.element.im))
}
throw new Error(`Don't know how to neg type ${object.type}`)
}

Expand All @@ -19,6 +25,11 @@ export const inv = (object) => {
if (object.type === FRACTION) {
return createFraction(object.element.den, object.element.num)
}
if (object.type === COMPLEX) {
const { re, im } = object.element
const den = add(mult(re, re), mult(im, im))
return createComplex(divideFract(re, den), neg(divideFract(im, den)))
}
throw new Error(`Don't know how to invert type ${object.type}`)
}

Expand All @@ -42,19 +53,46 @@ export const add = (object1, object2) => {
)
}
if (object1.type === NUMBER && object2.type === FRACTION) {
return add(createFraction(object1, createNumber(1)), object2)
if (isNumberInteger(object1)) {
return add(createFraction(object1, createNumber(1)), object2)
} else {
return add(object1, createNumber(object2.element.num.element / object2.element.den.element))
}
}
if (object1.type === FRACTION && object2.type === NUMBER) {
return add(object1, createFraction(object2, createNumber(1)))
if (isNumberInteger(object2)) {
return add(object1, createFraction(object2, createNumber(1)))
} else {
return add(createNumber(object1.element.num.element / object1.element.den.element), object2)
}
}
if ((object1.type === NUMBER || object1.type === FRACTION) && object2.type === COMPLEX) {
return add(createComplex(object1, createNumber(0)), object2)
}
if (object1.type === COMPLEX && (object2.type === NUMBER || object2.type === FRACTION)) {
return add(object1, createComplex(object2, createNumber(0)))
}
if (object1.type === COMPLEX && object2.type === COMPLEX) {
const { re: re1, im: im1 } = object1.element
const { re: re2, im: im2 } = object2.element
return createComplex(add(re1, re2), add(im1, im2))
}
Error(`Don't know how to add types [${object1.type}] and [${object2.type}]`)
}

export const sub = (object1, object2) => {
if (object1.type === STRING || object2.type === STRING) {
throw new Error(`Can't sub types [${object1.type}] and [${object2.type}]`)
}
if (object1.type === FRACTION && object2.type === NUMBER) {
return add(object1, createFraction(neg(object2), createNumber(1)))
if (isNumberInteger(object2)) {
return add(object1, createFraction(neg(object2), createNumber(1)))
} else {
return add(createNumber(object1.element.num.element / object1.element.den.element), neg(object2))
}
}
if (object1.type === COMPLEX && (object2.type === NUMBER || object2.type === FRACTION)) {
return add(object1, createComplex(neg(object2), createNumber(0)))
}
return add(object1, neg(object2))
}
Expand All @@ -76,21 +114,52 @@ export const mult = (object1, object2) => {
)
}
if (object1.type === NUMBER && object2.type === FRACTION) {
return mult(createFraction(object1, createNumber(1)), object2)
if (isNumberInteger(object1)) {
return mult(createFraction(object1, createNumber(1)), object2)
} else {
return createNumber(object1.element * (object2.element.num.element / object2.element.den.element))
}
}
if (object1.type === FRACTION && object2.type === NUMBER) {
return mult(object1, createFraction(object2, createNumber(1)))
if (isNumberInteger(object2)) {
return mult(object1, createFraction(object2, createNumber(1)))
} else {
return createNumber((object1.element.num.element / object1.element.den.element) * object2.element)
}
}
if ((object1.type === NUMBER || object1.type === FRACTION) && object2.type === COMPLEX) {
return mult(createComplex(object1, createNumber(0)), object2)
}
if (object1.type === COMPLEX && (object2.type === NUMBER || object2.type === FRACTION)) {
return mult(object1, createComplex(object2, createNumber(0)))
}
if (object1.type === COMPLEX && object2.type === COMPLEX) {
const { re: re1, im: im1 } = object1.element
const { re: re2, im: im2 } = object2.element
return createComplex(sub(mult(re1, re2), mult(im2, im1)), add(mult(re1, im2), mult(re2, im1)))
}
Error(`Don't know how to mult types [${object1.type}] and [${object2.type}]`)
}

export const divide = (object1, object2) => {
if (object1.type === STRING || object2.type === STRING) {
throw new Error(`Can't divide types [${object1.type}] and [${object2.type}]`)
}
if (object1.type === FRACTION && object2.type === NUMBER) {
return mult(object1, createFraction(createNumber(1), object2))
if (isNumberInteger(object2)) {
return mult(object1, createFraction(createNumber(1), object2))
} else {
return createNumber((object1.element.num.element / object1.element.den.element) / object2.element)
}
}
return mult(object1, inv(object2))
}

exportOnWindow({add})
export const divideFract = (object1, object2) => {
if (object1.type === NUMBER && object2.type === NUMBER && isNumberInteger(object1) && isNumberInteger(object2)) {
return createFraction(object1, object2)
}
return divide(object1, object2)
}

exportOnWindow({ add })
39 changes: 39 additions & 0 deletions src/slices/rplSlice/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { FRACTION, NUMBER } from "./objectTypes";

export const compute_pgcd = (a, b) => {
a = Math.floor(Math.abs(a));
b = Math.floor(Math.abs(b));
if (b > a) {
var tmp = a;
a = b;
b = tmp;
}
while (true) {
if (b === 0) return a;
a %= b;
if (a === 0) return b;
b %= a;
}
}

export const getSign = (x) => {
if (x.type === NUMBER) {
if (x.element === 0) {
return 0
} else if (x.element > 0) {
return 1;
} else {
return -1;
}
}
if (x.type === FRACTION) {
if (x.element.num.element === 0) {
return 0;
} else if (x.element.num.element > 0) {
return 1;
} else {
return -1;
}
}
}

0 comments on commit 5d90af4

Please sign in to comment.