Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Show HN: Calc, a simple command-line calulator in C (github.com/vivekannan)
31 points by vivekkannan on June 9, 2015 | hide | past | favorite | 48 comments


Very cool. But let me warn you: Your method of code organization won't scale to anything whose #include graph doesn't form a tree. You should learn how to put your declarations into include-guarded header files and your implementations into C files.

Also, abs has signature int abs(int x). I think you want to use fabs.

Edit: Also, it is quite peculiar to have the program print out null characters. The output for ./calc '2' is four characters: {'2', '\0', '\0', '\n'}. In places where you're using %c and printing '\0', you could use %s and print "" instead.


Regarding code organization ... Any learning sources where some approaches are being explained ? I am sysadmin and sometimes I find it hard to understand how source files I just downloaded connected to each other. Thx


Any book on C. The early chapters of an online C tutorial.


I just checked the first 5 C tutorials the showed up in Google and non of them talked about code organization in anything but the most superficial sense.



search for 'C best practices'. I've even seen a guideline from Linus Torvalds a few months ago.


fixed both. thanks for pointing it out. I have just been told about wacky header files. will changing calc.h to calc.c make things less wackier?


The header file should basically only contain:

* The dependencies (includes) of the implentation

* A list of function signatures, data structures it provides, etc

* Any constant definition specific to the module

Another "feature" of separating the header file from the implementation is that it guards against recursive includes, as the commenter above suggested.

So your calc.h and calc.c should look something like this: https://gist.github.com/jdiez17/2f093b2c8cc2913bb890


The header file should not have the includes that the implementation uses. It should have the minimal necessary to achieve its purposes as a header file.


Why? Most of those headers are going to be guarded against multiple inclusions anyway, and it's a nice way to list the dependencies of the module.


It is leaking includes without any reason, there is no need for the user of given module to know what dependencies the implementation is using. Place to list dependencies is at the beginning of the source file of given module, and as far as I know this is the convention.

Main problem with dependencies in the headers is that if any header of your library include headers from the different libraries the user will have have to (1) have those headers, (2) probably configure their build to use them, even if they bring zero useful information for them.


They're not there for "no reason". For example, if I use a type defined in some external header, I definitely need to include that in the .h, so I might as well just include everything else.

Besides, in my mind it's the logical place to put it: the header file is almost like the "spec" or the "coversheet" of a .c. It includes the name, the authors, possibly a description of what it does, a list of functions and datatypes, so why not a list of dependencies as well?


The header role is to define the interface of the module, that's why I believe the leaner the header is the better. When I'm using a module I do not care what the implementation is using, the only thing I care about is what functions are exposed and what data structures are required by the functions. All other details are irrelevant. If any of the functions are using any types from the external header that are passed by value (for pointers forward declaration is the way to go), then that headers also become part of the interface and have to be #included in header.


For C, I'd say the biggest benefit is that you can absorb a clearer picture of how parts of your project are using other parts, because your .c files have to directly include the headers they use. It's not perfect, because you won't be perfectly disciplined and can't use pointer indirection for every single type, but it is often very useful if you can immediately see a short list of .c files that could possibly access the interface exposed by a particular header file.

Another reason is namespace pollution (if only for better autocomplete), and another is that you don't walk yourself into a situation where editing one header file suddenly gives you a zillion name lookup errors (because that's mildly annoying).

In C++, the biggest and most substantial benefit can be the effect on compile times.


>For C, I'd say the biggest benefit is that you can absorb a clearer picture of how parts of your project are using other parts, because your .c files have to directly include the headers they use.

It may just be that this is the specific way in which my mind works, but I find it easier to get a clearer picture of the file if I just read the .h. It would have a summary (in terms of comments and code, like function declarations) and a list of includes.

>it is often very useful if you can immediately see a short list of .c files that could possibly access the interface exposed by a particular header file.

If you wanted to see which files use the interface in that header file directly, you can scan your headers for ones that include it explicitly, instead of working backward from the header. And a similar thing with the autocomplete problem: only load the header files mentioned in thing.h (assuming you're editing thing.c).

The increased compilation time is a good argument. I don't know any good way to mitigate it, but all these three problems sound like engineering problems to me, rather than a problem intrinsic to this approach.


> If you wanted to see which files use the interface in that header file directly, you can scan your headers for ones that include it explicitly, instead of working backward from the header.

Your .c files that use the interface will often be including it implicitly though, projects very quickly drift in that direction.


He is referring to the lack of guards in your header files(#ifdef NAME, #define NAME, #endif). That will prevent the preprocessor to include the file twice so your compiler can be happy.


How is this different from the existing command-line calculator 'bc'?

    echo "3+4" | bc 
    7
Other than not needing equations to be piped to it?

Note also that bc allows defining functions (among other features), and is also arbitrary precision (factorial of 200 below):

    $ echo "define f (x) { if (x <= 1) return (1); return (f(x-1) * x); }; f(200)"| bc
    78865786736479050355236321393218506229513597768717326329474253324435\
    94499634033429203042840119846239041772121389196388302576427902426371\
    05061926624952829931113462857270763317237396988943922445621451664240\
    25403329186413122742829485327752424240757390324032125740557956866022\
    60319041703240623517008587961789222227896237038973747200000000000000\
    00000000000000000000000000000000000


Writing your own calculator is a nice programming exercise (touches a lot of different problems and is still a small, manageable task), especially for languages you're not familiar with. Of course there's better ones for "production" use.


Obviously, bc is much more capable than calc. However, calc was not created to replace or even compete with bc or any other awesome command-line calculators. calc is my way of learning a bunch of stuffs (shunting yard).


First learning tip:

.h files are not the place for definitions.


duly noted. will change it ASAP.


You can use bc interactively just fine, no needing to pipe anything.

    $ bc
    3+4
    7
    ^D
    $


This one doesn't have abitrary precision?


That function syntax is a nice example of C-isms bleeding through. If I want to write some arithmetic, I want something more declarative and succinct than C.


Give the venerable dc a try. It can be quite succinct.

http://en.wikipedia.org/wiki/Dc_%28computer_program%29


It's weird that most of the code is in the header files.. also do you check for divide by zero?

Try to rewrite the whole thing as a single recursive function with two arguments (rest of input string, precedence of left side). You could use longjmp/setjmp to abort when there are errors.


I checked division by zero initially, but then realised that 1/0 causes double to represent inf. Thought that this could be useful(?) in some situations. For example, atan(1/0) --> 1.57... instead of error.


I was thinking to catch errors which could crash the program. Actually you should install a signal handler for SIGFPE to take care of this.


So being barely able to write C is now Hacker News-worthy?


I assume you posted this looking for feedback, so here is my advices:

- A 16-line main.c with a single function that does almost nothing is annoying to say the least; this application is simple enough that I think it really shouldn't be more than a single file.

- You really don't need to use dynamic allocation for this. Try a simpler algorithm like recursive-descent/precedence climbing, and tokenise+evaluate as you go.

- isSymbol()/getSymbol() should be merged into one function - you are doing duplicate work here since once you know that it is one of those special identifiers, you should be able to return the value immediately instead of doing another set of comparisons.

This is an almost-complete C interpreter in roughly the same number of lines of code as your calculator, but its structure is actually much simpler. It's probably a bit far toward the side of terseness, but good to study in any case:

https://github.com/rswier/c4/blob/master/c4.c

For understanding precedence climbing/recursive descent, this is a good article:

https://www.engr.mun.ca/~theo/Misc/exp_parsing.htm

One thing I see with a lot of beginners is a tendency to overcomplicate solutions, writing far more code than necessary. It takes practice to avoid this, but giving them some extremely minimal implementations to study can help them to better understand the essence of the problem and its solution; to a beginner, it is often simpler than they seem to be at first glance.


I've been using a perl-based calc for a number of years now. It started as a one liner eval-ing expressions on the command line. My favorite feature is that if symlinked as "hex", "oct", or "bin", it prints the output in hexadecimal, octal, or binary. And, because the evaluation is done by perl, hexadecimal, octal, and binary numbers can be entered using "0x", "0", and "0b" prefixes. A minor improvement was the substitution of commas with underscores, which perl allows in numbers for easier readability. Entering "1,000,000" is much nicer on the eyes than carefully counting the number of trailing zeros.

Two weeks ago, after becoming fed up with having to copy and paste results from one line to the next, I wrapped it in a repl and added bc(1)-like support for "." expanding to the last value. Rather embarrassingly, I started to implement bc(1)-style variables, but then realized this was incredibly silly as you can simply use perl variables directly. :-)

https://github.com/mct/junkdrawer/commits/master/bin/calc

I fully support writing other calc implementations, especially in languages such as C! It's a great exercise, and can become very complex very quickly. bc(1) supports arbitrary precision (try: echo "scale=500; 4a(1) 42" | bc -l), which is super awesome. In C, I've only implemented RPN calculators, never infix, which would be fun to try.


Actually strtod will convert 0x to hex.. It would be nice if it had an option to ignore commas.


Several months ago, I was looking for something like this. Then I started using bc but wasn't enough for me then I thought why not Python? It has tons of math and statistics libraries/functions and easier than shell script with bc.

So I started using Python by simply typing Python and do my stuff. It is fast enough and super productive.


FWIW, I use awk, which is POSIX standard

    $ awk 'BEGIN{print sqrt(2)+4;}'; 
    5.41421
bc is also standard but for some arbitrary reason I don't like it.


Another option would be to use the language called "R". I'm fairly new to it but its pretty much a programming language for math. Lots of libraries. Great programmatic graphing capability (ggplot2). Easy to dump file data into it.

In my lab a lot of biologists run there data with it. I think something like 80% of the graphs they publish are created in it. Better then excel in my opinion.

R, and Rstudio is what I've been using.


Suggestion: Move your business logic into .c files. Headers is there for other reasons.

Take a look at, http://embeddedgurus.com/barr-code/2010/11/what-belongs-in-a...


Nice. Mucked about with Boost Spirit's examples once to do something similar in one grammar-driven file: http://agentzlerich.blogspot.com/2011/06/using-boost-spirit-...

Final production version with better error reporting is under "expr*" files at https://github.com/RhysU/suzerain/tree/master/suzerain


Someone did a nice calculator hack a little while back, based upon an old reddit comment of mine:

http://tlrobinson.net/blog/2007/12/presenting-gccalc-a-horri...

http://www.reddit.com/r/programming/comments/62v70/first_cla...


Nice!

Suggestion: you could probably use `asprintf` to malloc the input expression string and use the Stopif variadic macro to return an error if the input takes up more than the available memory. Unless of course your target platform doesn't have the GNU libraries in which case you could write a test macro and insert your own implementation using `vsnprintf`. :)

You might also want to check your input lengths for reasonable size before allocating memory for them.

My first stop learning a new language is usually a project like this or a guess-the-number game. I hope it's been helpful to you.


Interesting, this is basically a very simple interpreter with a REPL. You should look into interpreters if this is something you enjoyed creating, there are many efficient and simple algorithms you could use to make this program a lot simpler and even more advanced too. For instance, recursive descent parsing, and the shunting yard algorithm.


Your value of pi is incorrect in the code, comments and readme. pi is 3.1415926... (and not 3.14151926...).


changed it. thanks.


For your amusement: a calculator written in BASIC:

https://github.com/jhallen/joes-sandbox/blob/master/snippets...


FYI, a Go version was announced not too long ago also: https://github.com/alfredxing/calc


I prefer explicit parenthesis for functions. That way you won't bump into much trouble if you want to implement functions with multiple arguments.


Please don't put the functions themselves in .h files, only the prototypes





Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: