Jump to content

Make a Calculator

Wictorian

So I have been wanting to build a calculator. I really have done a non-negligible amount of research about it but I couldn't find someone talking about an actual calculator. Apparently a 2022 calculator can only operate with 2 numbers. I want to build an actual calculator and it's not too easy. I don't want to follow an exact tutorial but building a parser for the first time is challenging and a guide would be helpful if it exists. So do you know of any such thing?

 

Link to comment
Share on other sites

Link to post
Share on other sites

1 hour ago, shadow_ray said:

Use the reverse Polish notation. Much easier to parse, it doesn't require a parse tree.

But how does priority work with this?

 

Also am I supposed to come up with something similiar to this myself or use an existing one?

Link to comment
Share on other sites

Link to post
Share on other sites

I don't understand what the problem is.

You have the order of operations : https://en.wikipedia.org/wiki/Order_of_operations

Decide on what functions your calculator should support ...  then parse the entered text, ignore characters that are invalid, keep track of paranthesis so that you would parse whatever's between paranthesis first and so on...

 

Have you looked at calc.exe in Windows and the various modes it has? See scientific mode, programmer mode etc..

Link to comment
Share on other sites

Link to post
Share on other sites

I think everything depends on what you mean by making a calculator.

 

It seems like you want to do things like "(5*3 + 2) * 2 / 3"

 

At that stage, it's whether or not you want to create your own parser or use a prebaked one.  Overall I'd say trying to create your own parser is a good way to kind of just learn.

 

Ultimately parsing shouldn't be too difficult, give that you have a very very limited character set you are working with.  You could do things like creating a parse tree and such, but as a starting task I'd just use recursion...it will teach you more (although overall recursion shouldn't really be relied on as it's often more inefficient and eats up the stack).

 

Like ultimately in terms of parsing, you can do everything in phases (assuming python)

Have a list, adding the ()+-*/ numbers all into a list

Searching for ()'s (and reducing the ()'s down to numbers). [Getting the index of each]

Searching for * and / and reducing things down

Then finally handling the + and -.

 

Example

(15*3+(1+2*3))*2 / 3

The list would look like [(, 15, *, 3, +, (,1,+,2,*,3,),),.....]

 

This gets sent into a function called like find_value

 

Start left to right indexing where the ( is and when you hit a ) pop the last (...you now know a set of () that you can process.

 

You can then send the portion of the array belonging to the array to the find_value...and replace those

So in our example open_par_list = [0, 5].  Then sees the ) at index 11.  So pop off the last one so you have open_par_list = [0], and you have the pair 5 - 11 as a parenthesis you need...but you don't want to pass in the () so it would be 5+1 (6) and 11-1 (10).  So new_equation = equation[6:10].

Then you can find_value(new_equation) (and replace the items in equation with the result)

 

You now have the list [1,+,2,*,3] as an equation.  So looking left to right, look for * and /.  And modify the list.

e.g. found * at index 3.  So you know the value will be new_value = equation[3-1]*equation[3+1].  Which in this case is 6  Remove the items from the list and replace it with a 3...so now your equation = [1,+,6].

If the list size ever is 1 then you can just return a result.

 

Now with [1,+,6] You can't find any * or /...so you move to processing + and - left to right.  Same concept as * and /.  When you see a + look one left and one right.  Then add.

So in this case + index = 1, so new_value = equation[1-1] + equation[1+1]

equation = [7]  Size of one, so return 7 as the result.

 

We now are out of the find_value that was called from find_value...so replace the () portion with the found value of 7

[(, 15, *, 3, +, 7, ), 2,/,3]

The open_par_list local variable still = [0]...we now find the ) at index 6.  So now call find_value([15, *, 3, +, 7])

From here hopefully you get the idea.  For the () you essentially use recursion...because it makes things slightly simpler in terms of figuring out the process.

 

 

I hope this at least helps...I'm not sure if it makes sense to someone reading it...but it's the way my brain works when approaching this problem...the key is if you see a - and the last index you added when initially parsing means it's a negative number not an operation

 

3735928559 - Beware of the dead beef

Link to comment
Share on other sites

Link to post
Share on other sites

Let's say you have an expression like   100 + (5*3 + (50-10)/4 - 2) * 2 / 3

 

You would want to look at how many ( characters are, and how many ) characters are.. if there's a mismatch you would either add the ) characters to the end of the expression, or throw an error. 

 

A simple parse would have a "level" variable, where the initial level is 0 and each time a "(" is encountered, you increase the level so the formula above becomes 

level 0  100 + 

level 1            5*3 + 

level 2                      50-10

levei 1            /4 -2 

level 0   * 2 /3  

 

Then you could go the highest level to the lowest level and parse each as a separate expression  and you would convert it into 

%EXP2% = 50-10

%EXP1% =  5*3 + %EXP2%/4 - 2

%EXP0% = 100+%EXP1%*2/3

 

So then your parse calculates EXP2  (40), replaces %EXP2% in string or array of parsed elements with 40 , then calculates EXP1, then calculates EXP0

 

You can parse the expression as an array of items where item could be number , operator (+-/*) , expression tags, 

 

You could expand the parser to support functions like  sqrt or sin or pow by giving a special character ex   [sin]  or by looking for a sequence of characters before each ( character to figure if that is the name of a supported function ... ex pow (100,3)  = 100 - you find the "(" then go backwards if there's alpha characters (a-zA-Z) until it's not an alpha character  ... so in this case besides the "level" attribute you'd also need a "transform" attribute of some sort  (ex default attribute is mathematic expression,  otherwise the transform would be the name of the function ex sin, cos, tan, pow, sqrt, whatever, and internally you'd have some table with the restrictions for each of these functions  like  for example pow (short for power) must always have 2 expressions (separated by , )  unless you default to power of 2 if the second attribute is missing,  or sqrt must always have only one expression between ()  and so on

 

 

Link to comment
Share on other sites

Link to post
Share on other sites

1 hour ago, wanderingfool2 said:

I think everything depends on what you mean by making a calculator.

 

It seems like you want to do things like "(5*3 + 2) * 2 / 3"

 

At that stage, it's whether or not you want to create your own parser or use a prebaked one.  Overall I'd say trying to create your own parser is a good way to kind of just learn.

 

Ultimately parsing shouldn't be too difficult, give that you have a very very limited character set you are working with.  You could do things like creating a parse tree and such, but as a starting task I'd just use recursion...it will teach you more (although overall recursion shouldn't really be relied on as it's often more inefficient and eats up the stack).

 

Like ultimately in terms of parsing, you can do everything in phases (assuming python)

Have a list, adding the ()+-*/ numbers all into a list

Searching for ()'s (and reducing the ()'s down to numbers). [Getting the index of each]

Searching for * and / and reducing things down

Then finally handling the + and -.

 

Example

(15*3+(1+2*3))*2 / 3

The list would look like [(, 15, *, 3, +, (,1,+,2,*,3,),),.....]

 

This gets sent into a function called like find_value

 

Start left to right indexing where the ( is and when you hit a ) pop the last (...you now know a set of () that you can process.

 

You can then send the portion of the array belonging to the array to the find_value...and replace those

So in our example open_par_list = [0, 5].  Then sees the ) at index 11.  So pop off the last one so you have open_par_list = [0], and you have the pair 5 - 11 as a parenthesis you need...but you don't want to pass in the () so it would be 5+1 (6) and 11-1 (10).  So new_equation = equation[6:10].

Then you can find_value(new_equation) (and replace the items in equation with the result)

 

You now have the list [1,+,2,*,3] as an equation.  So looking left to right, look for * and /.  And modify the list.

e.g. found * at index 3.  So you know the value will be new_value = equation[3-1]*equation[3+1].  Which in this case is 6  Remove the items from the list and replace it with a 3...so now your equation = [1,+,6].

If the list size ever is 1 then you can just return a result.

 

Now with [1,+,6] You can't find any * or /...so you move to processing + and - left to right.  Same concept as * and /.  When you see a + look one left and one right.  Then add.

So in this case + index = 1, so new_value = equation[1-1] + equation[1+1]

equation = [7]  Size of one, so return 7 as the result.

 

We now are out of the find_value that was called from find_value...so replace the () portion with the found value of 7

[(, 15, *, 3, +, 7, ), 2,/,3]

The open_par_list local variable still = [0]...we now find the ) at index 6.  So now call find_value([15, *, 3, +, 7])

From here hopefully you get the idea.  For the () you essentially use recursion...because it makes things slightly simpler in terms of figuring out the process.

 

 

I hope this at least helps...I'm not sure if it makes sense to someone reading it...but it's the way my brain works when approaching this problem...the key is if you see a - and the last index you added when initially parsing means it's a negative number not an operation

 

Yeah definitely helps, thanks.

Link to comment
Share on other sites

Link to post
Share on other sites

36 minutes ago, mariushm said:

Let's say you have an expression like   100 + (5*3 + (50-10)/4 - 2) * 2 / 3

 

You would want to look at how many ( characters are, and how many ) characters are.. if there's a mismatch you would either add the ) characters to the end of the expression, or throw an error. 

 

A simple parse would have a "level" variable, where the initial level is 0 and each time a "(" is encountered, you increase the level so the formula above becomes 

level 0  100 + 

level 1            5*3 + 

level 2                      50-10

levei 1            /4 -2 

level 0   * 2 /3  

 

Then you could go the highest level to the lowest level and parse each as a separate expression  and you would convert it into 

%EXP2% = 50-10

%EXP1% =  5*3 + %EXP2%/4 - 2

%EXP0% = 100+%EXP1%*2/3

 

So then your parse calculates EXP2  (40), replaces %EXP2% in string or array of parsed elements with 40 , then calculates EXP1, then calculates EXP0

 

You can parse the expression as an array of items where item could be number , operator (+-/*) , expression tags, 

 

You could expand the parser to support functions like  sqrt or sin or pow by giving a special character ex   [sin]  or by looking for a sequence of characters before each ( character to figure if that is the name of a supported function ... ex pow (100,3)  = 100 - you find the "(" then go backwards if there's alpha characters (a-zA-Z) until it's not an alpha character  ... so in this case besides the "level" attribute you'd also need a "transform" attribute of some sort  (ex default attribute is mathematic expression,  otherwise the transform would be the name of the function ex sin, cos, tan, pow, sqrt, whatever, and internally you'd have some table with the restrictions for each of these functions  like  for example pow (short for power) must always have 2 expressions (separated by , )  unless you default to power of 2 if the second attribute is missing,  or sqrt must always have only one expression between ()  and so on

 

 

So is this the parse tree people are talking about? I actually had this idea.

Link to comment
Share on other sites

Link to post
Share on other sites

7 hours ago, Wictorian said:

So is this the parse tree people are talking about? I actually had this idea.

Kind of.

What you are looking for is the "Shunting yard algorithm" for infix to postfix notation conversion [infix notation = what we write, postfix = reverse polnish notation].

 

Wikipedia has pseudo code for this and I'm sure there are examples in almost every language somewhere 😄

Link to comment
Share on other sites

Link to post
Share on other sites

9 hours ago, Wictorian said:

Yeah definitely helps, thanks.

I just want to clarify, this is like an "initial" kind of solution to the problem.

 

There are better ways of solving and parsing, but I think the way I describe is the least involved in regards to programming it (otherwise you need to worry other stuff, which as a beginner I don't know if it's appropriate to do it...as it's better to start with something that is definitely achievable and then after implementing something like that once, you get to learn things like how to debug and stuff; so the next time you tackle a more complex project it's just in general easier)

3735928559 - Beware of the dead beef

Link to comment
Share on other sites

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×