Hacker News new | past | comments | ask | show | jobs | submit login
Show HN: Shellmath: Floating-point arithmetic directly in bash (github.com/clarity20)
30 points by clarity20 on Feb 25, 2021 | hide | past | favorite | 18 comments



Hello, all!

I'm proud to present shellmath, a floating-point calculator written entirely in bash.

Shellmath does not call out to any external programs -- calculators, text manipulators, or anything else -- and achieves execution speeds competitive with GNU awk and bc.

You can do floating-point math directly in the shell, and now you have a ready-made family of routines for doing so!

For skeptics ;-) I've included a demo that calculates 'e'. I've also posted a few words about the methodology, runtime efficiency, and a few bells and whistles on the README and in the project wiki.

I eagerly await your feedback!

Be good, and have a great day!


This is awful¹, I love it!

Thumbing through the code made me wonder what the zsh(my normal shell) implementation feels like, and I think it is fair to say your code is easier to wrap your head around than the documentation for floats in zsh.

FWIW - and given that you've only shown Windows timings - on my Linux box hyperfine shows:

    Summary
      './faster_e_demo.sh 15' ran
        1.78 ± 0.16 times faster than './slower_e_demo.sh 15'
I expected the speeds to be closer because of the differences with subshells on Windows and Linux, but the distance still surprised me.

¹ https://en.m.wiktionary.org/wiki/contranym - choose one


Thank you, I tried to make the code as readable as I could. And yes, since the *nix model for (sub)processes is cleaner than Cygwin-on-Windows it's kind of surprising that that gap is what it is.


Who does the name in the description refer to?



;-)


> Ad astra per aspera.

And yet reliance on Bash extensions rather than doing it in pure POSIX shell. ;)

A quick skim suggests the biggest convenience is using local variables to make recursion easier. But it's fairly easy to implement push and pop routines to turn global variables into a stack. You could even name push "local" and keep much the same syntax. Better would be an indirect call function which saves and restores a set of global "registers" across invocations, avoiding the need for explicit pops in each function.

Many of the pattern matching parts can be easily replaced with the canonical "fnmatch" shell implementation[1], which uses a case statement to provide in-shell fnmatch(3) emulation. If that's not powerful enough, expr supports regular expressions, though while usually a built-in it's not necessarily so. In the absence of regular expressions it's usually sufficient to use parameter expansion facilities to split strings and use simpler glob pattern matching on the components.

Still very cool. If I get bored one day I might try refactoring to make it pure POSIX shell.

Because Linux distributions (not to mention containers) are increasingly removing common shell utilities like bc, if this were extended to arbitrary precision arithmetic it might even be legitimately useful! For similar reasons I've implemented a base64 encoder and decoder using mostly pure POSIX shell--still depends on od(1) or dd(1) so it can do proper binary I/O, but the low-level coding is done using shell arithmetic and POSIX looping constructs.

[1] See http://www.etalabs.net/sh_tricks.html


I'm afraid I don't have the wherewithal to do this in the Bourne shell! An explicit call stack using those globals is a neat idea. Generally the [[ =~ ]] construct is my go-to for string parsing. Can you do everything you need with parameter expansion and string globbing alone (without turning on "extglob" swince that would be cheating)? Finally, is your encoder/decoder posted online?


"Because Linux distributions (not to mention containers) are increasingly removing common shell utilities like bc"

Hmm that sounds fair, but if you can't do something simple like get bc why would you expect to be able to get this?


Very nice writeup!

Related: I got a couple mails about raytracers in pure bash. It looks like they use fixed point arithmetic in bash rather than floating point, which works well:

http://www.oilshell.org/blog/2021/01/audio-and-graphics.html...

https://github.com/aneeshdurg/bash-raytracer

https://www.bouledef.eu/~tleguern/articles/shell-fixedpoint/


Thank you, and a tip of the hat to them for doing square roots the hard way.


> In the spirit of Bash, numerical overflow is silently ignored.

I love that!


so ... anyone have a floating-point library for sed(1)?

(this is a serious question. compare: http://sed.sourceforge.net/grabbag/tutorials/lookup_tables.t... and https://en.wikipedia.org/wiki/IBM_1620#Transferred_to_San_Jo... )


Why should I use `_shellmath_divide 1 $zero_factorial` over `bc 1 / $zero_factorial`?

It's imprecise and much more clunky to write. bc exists for decades already and has arbitrary precision.


I think shellmath exists because it could be done, rather than because it needed to be done.

Also, `bc 1 / 2` causes an error (at least in bash 3.2 using bc version 1.06) because file 1 doesn't exist, so there's also that for a reason


Yes, it's a POC for anyone who might want to use it or expand on it.


Well, anything’s better than multiplying the float by ten to the power of the number of decimals.


Hmm. Cool?

I guess I don't expect my shell to do much, I reach for numpy or something when I want fancier math.

Not to knock, but what's the use case? I think I prefer bc - "10/(200.7-73.6)" is easy to look at, I don't mind the extra dependency for that.

Actually on that note what I do need to find is a proper calculator on Android, what I have found are computer algebra systems with arcane learning curves. Its surprising how bad the default calc is and nobody had made a popular good alt...




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

Search: