File: ca.sh
   1 #!/bin/sh
   2 
   3 # The MIT License (MIT)
   4 #
   5 # Copyright (c) 2026 pacman64
   6 #
   7 # Permission is hereby granted, free of charge, to any person obtaining a copy
   8 # of this software and associated documentation files (the "Software"), to deal
   9 # in the Software without restriction, including without limitation the rights
  10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11 # copies of the Software, and to permit persons to whom the Software is
  12 # furnished to do so, subject to the following conditions:
  13 #
  14 # The above copyright notice and this permission notice shall be included in
  15 # all copies or substantial portions of the Software.
  16 #
  17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  23 # SOFTWARE.
  24 
  25 
  26 # ca [options...] [expressions...]
  27 #
  28 #
  29 # CAlculator is an easier-to-use way of running `bc` (basic calculator) where
  30 #
  31 #   - you can calculate multiple different things in one run
  32 #   - you give the expressions as arguments, while `bc` uses stdin
  33 #   - you don't need quoting when avoiding parentheses and spaces
  34 #   - you can use either ** or ^ to raise powers
  35 #   - you can use [ and ] or ( and ) interchangeably
  36 #   - the number of max-accuracy decimals is 25 by default
  37 #   - automatically includes the extended bc math library via option -l
  38 #   - there are several extra predefined values, functions, and aliases
  39 #   - unneeded trailing decimal zeros are ignored for final outputs
  40 #
  41 # The options are, available both in single and double-dash versions
  42 #
  43 #   -h, -help       show this help message
  44 #   -v, -verbose    show an ANSI-styled banner for each command being run
  45 
  46 
  47 verbose=0
  48 
  49 case "$1" in
  50     -h|--h|-help|--help)
  51         awk '/^# +ca /, /^$/ { gsub(/^# ?/, ""); print }' "$0"
  52         exit 0
  53     ;;
  54 
  55     -v|--v|-verbose|--verbose) verbose=1; shift ;;
  56 
  57     -) ;;
  58 
  59     --) shift ;;
  60 
  61     -*)
  62         printf "%s: unsupported option %s\n" "$0" "$1" >&2
  63         exit 1
  64     ;;
  65 esac
  66 
  67 if [ $# -eq 0 ]; then
  68     awk '/^# +ca /, /^$/ { gsub(/^# ?/, ""); print }' "$0" >&2
  69     exit 1
  70 fi
  71 
  72 # default max-accuracy decimals to use for calculations
  73 scale=25
  74 
  75 src='
  76 femto = 0.000000000000001;
  77 pico = 0.000000000001;
  78 nano = 0.000000001;
  79 micro = 0.000001;
  80 milli = 0.001;
  81 
  82 kilo = 1000;
  83 mega = 1000 * kilo;
  84 giga = 1000 * mega;
  85 tera = 1000 * giga;
  86 peta = 1000 * tera;
  87 exa = 1000 * peta;
  88 zetta = 1000 * exa;
  89 
  90 binkilo = 1024;
  91 binmega = 1024 * binkilo;
  92 bingiga = 1024 * binmega;
  93 bintera = 1024 * bingiga;
  94 binpeta = 1024 * bintera;
  95 binexa = 1024 * binpeta;
  96 binzetta = 1024 * binexa;
  97 
  98 kb = 1024;
  99 mb = 1024 * kb;
 100 gb = 1024 * mb;
 101 tb = 1024 * gb;
 102 pb = 1024 * tb;
 103 eb = 1024 * pb;
 104 zb = 1024 * eb;
 105 
 106 kib = 1024;
 107 mib = 1024 * kib;
 108 gib = 1024 * mib;
 109 tib = 1024 * gib;
 110 pib = 1024 * tib;
 111 zib = 1024 * pib;
 112 
 113 au = 149597870.7
 114 mol = 602214076000000000000000;
 115 mole = 602214076000000000000000;
 116 
 117 cup = 0.23658824;
 118 cup2l = 0.23658824;
 119 floz2l = 0.0295735295625;
 120 floz2ml = 29.5735295625;
 121 ft = 0.3048;
 122 ft2m = 0.3048;
 123 gal = 3.785411784;
 124 gal2l = 3.785411784;
 125 in = 2.54;
 126 in2cm = 2.54;
 127 lb = 0.45359237;
 128 lb2kg = 0.45359237;
 129 mi = 1.609344;
 130 mi2km = 1.609344;
 131 mpg = 0.425143707;
 132 mpg2kpl = 0.425143707;
 133 nm = 1.852;
 134 nm2km = 1.852;
 135 nmi = 1.852;
 136 nmi2km = 1.852;
 137 oz2g = 28.349523125
 138 psi2pa = 6894.757293168;
 139 ton = 907.18474;
 140 ton2kg = 907.18474;
 141 yd = 0.9144;
 142 yd2m = 0.9144;
 143 
 144 ga2l = gal2l;
 145 nm2km = nmi2km;
 146 tn2kg = ton2kg;
 147 
 148 million = 1000000;
 149 billion = 1000 * million;
 150 trillion = 1000 * billion;
 151 
 152 hour = 3600;
 153 day = 24 * hour;
 154 week = 7 * day;
 155 
 156 hr = hour;
 157 wk = week;
 158 
 159 /* function "choose": "bc" uses "c" for the built-in cosine function */
 160 
 161 define abs(x) { if (x >= 0) return (x) else return (-x); }
 162 define atan(x) { return (a(x)); }
 163 define bits(x) { return (log2(x)); }
 164 define ceil(x) { return (ceiling(x)); }
 165 define choose(n, k) { return (com(n, k)); }
 166 define circle(r) { return (4 * a(1) * r * r); } /* circle-area from radius */
 167 define circum(r) { return (8 * a(1) * r); } /* circumference from radius */
 168 define circumference(r) { return (8 * a(1) * r); }
 169 define com(n, k) { if (n < k) return (0) else return (per(n, k) / fac(k)); }
 170 define comb(n, k) { return (com(n, k)); }
 171 define combin(n, k) { return (com(n, k)); }
 172 define combinations(n, k) { return (com(n, k)); }
 173 define cos(x) { return (c(x)); }
 174 define cosh(x) { return ((e(x) + e(-x)) / 2); }
 175 define cot(x) { return (c(x) / s(x)); }
 176 define coth(x) { return ((e(x) + e(-x)) / (e(x) - e(-x))); }
 177 define dbin(x, n, p) { return (dbinom(x, n, p)); }
 178 define dbinom(x, n, p) { return (com(n, x) * (p ^ x) * ((1 - p) ^ (n - x))); }
 179 define deg(x) { return (180 * x / pi()); }
 180 define digits(x) { return (log10(x)); }
 181 define degrees(x) { return (deg(x)); }
 182 define dexp(x, r) { if (r < 0) return (0) else return (r * e(-r * x)); }
 183 define dpois(x, l) { return ((l ^ x) * e(-l) / fac(x)); }
 184 define gauss(x) { return (gaussian(x)); }
 185 define gaussian(x) { return (e(-(x * x))); }
 186 define epa(x) { return (epanechnikov(x)); }
 187 define eu() { return (e(1)); }
 188 define euler() { return (e(1)); }
 189 define exp(x) { return (e(x)); }
 190 define f(x) { return (fac(x)); }
 191 define fact(x) { return (fac(x)); }
 192 define factorial(x) { return (fac(x)); }
 193 define ftin(f, i) { return (0.3048 * f + 0.0254 * i); }
 194 define gcd(x, y) { return (x * y / lcm(x, y)); }
 195 define hypot(x, y) { return (sqrt(x*x + y*y)); }
 196 define j0(x) { return (j(0, x)); }
 197 define j1(x) { return (j(1, x)); }
 198 define lboz(l, o) { return (0.45359237 * l + 0.028349523 * o); }
 199 define ln(x) { return (l(x)); }
 200 define log(x) { return (l(x)); }
 201 define logistic(x) { return (1 / (1 + e(-x))); }
 202 define max(x, y) { if (x >= y) return (x) else return (y); }
 203 define min(x, y) { if (x <= y) return (x) else return (y); }
 204 define mix(x, y, k) { return (x * (1 - k) + y * k); }
 205 define mod1(x) { return (mod(x, 1)); }
 206 define modf(x) { return (mod(x, 1)); }
 207 define p(n, k) { return (per(n, k)); }
 208 define pbin(x, n, p) { return (pbinom(x, n, p)); }
 209 define perm(n, k) { return (per(n, k)); }
 210 define permut(n, k) { return (per(n, k)); }
 211 define permutations(n, k) { return (per(n, k)); }
 212 define pexp(x, r) { if (r < 0) return (0) else return (1 - e(-r * x)); }
 213 define pi() { return (4 * a(1)); }
 214 define pow2(x) { return (2 ^ x); }
 215 define power2(x) { return (2 ^ x); }
 216 define pow10(x) { return (10 ^ x); }
 217 define power10(x) { return (10 ^ x); }
 218 define prime(n) { return (isprime(n)); }
 219 define r(x, d) { return (round(x, d)); }
 220 define r0(x) { return (round0(x)); }
 221 define rad(x) { return (pi() * x / 180); }
 222 define radians(x) { return (rad(x)); }
 223 define sgn(x) { return (sgn(x)); }
 224 define sin(x) { return (s(x)); }
 225 define sinc(x) { if (x == 0) return (1) else return (s(x) / x); }
 226 define sinh(x) { return ((e(x) - e(-x)) / 2); }
 227 define tan(x) { return (s(x) / c(x)); }
 228 define tanh(x) { return ((e(x) - e(-x)) / (e(x) + e(-x))); }
 229 define tau() { return (8 * a(1)); }
 230 
 231 define ceiling(x) {
 232     auto s, r;
 233     s = scale;
 234     scale = 0;
 235     r = x % 1;
 236     scale = s;
 237     if (r == 0) return (x);
 238     if (x < 0) return (x - r);
 239     return (x - r + 1);
 240 }
 241 
 242 define epanechnikov(x) {
 243     if ((x < -1) || (x > 1)) return (0);
 244     return (3 / 4 * (1 - (x * x)));
 245 }
 246 
 247 define fac(x) {
 248     auto f, i;
 249     if (x < 0) return (0);
 250     f = 1;
 251     for (i = x; i >= 2; i--) f *= i;
 252     return (f);
 253 }
 254 
 255 define floor(x) {
 256     auto s, r;
 257     s = scale;
 258     scale = 0;
 259     r = x % 1;
 260     scale = s;
 261     if (r == 0) return (x);
 262     if (x < 0) return (x - r - 1);
 263     return (x - r);
 264 }
 265 
 266 define isprime(n) {
 267     auto i, m, s;
 268     if ((n % 1) != 0) return (0);
 269     if (n < 2) return (0);
 270 
 271     s = scale;
 272     scale = 0;
 273 
 274     if ((n % 2) == 0) {
 275         scale = s;
 276         return (n == 2);
 277     }
 278 
 279     m = sqrt(n);
 280     for (i = 3; i <= m; i += 2) {
 281         if ((n % i) == 0) {
 282             scale = s;
 283             return (0);
 284         }
 285     }
 286 
 287     scale = s;
 288     return (1);
 289 }
 290 
 291 define lcm(x, y) {
 292     auto a, b, z;
 293     a = x;
 294     b = y;
 295     if (a > b) {
 296         a = y;
 297         b = x;
 298     }
 299 
 300     /* the LCM is defined only for positive integers */
 301     if (mod(x, 1) != 0 || x < 1 || mod(y, 1) != 0 || y < 1) return (0);
 302 
 303     z = b;
 304     while (mod(z, a) != 0) z += b;
 305     return (z);
 306 }
 307 
 308 define log2(x) {
 309     auto r, n;
 310     if (x <= 0) return (l(x) / l(2));
 311 
 312     r = 0;
 313     for (n = x; n > 1; n /= 2) r += 1;
 314 
 315     if (n == 1) return (r);
 316     return (l(x) / l(2));
 317 }
 318 
 319 define log10(x) {
 320     auto r, n;
 321     if (x <= 0) return (l(x) / l(10));
 322 
 323     r = 0;
 324     for (n = x; n > 1; n /= 10) r += 1;
 325 
 326     if (n == 1) return (r);
 327     return (l(x) / l(10));
 328 }
 329 
 330 define mod(x, y) {
 331     auto s, m;
 332     s = scale;
 333     scale = 0;
 334     m = x % y;
 335     scale = s;
 336     return (m);
 337 }
 338 
 339 define per(n, k) {
 340     auto p, i;
 341     if (n < k) return (0);
 342     p = 1;
 343     for (i = n; i >= n - k + 1; i--) p *= i;
 344     return (p);
 345 }
 346 
 347 /* pbinom inefficiently repeats calculations for now, which keeps it simple */
 348 define pbinom(x, n, p) {
 349     auto k, t;
 350     t = 0;
 351     for (k = 0; k <= n; k++) t += dbinom(k, n, p);
 352     return (t);
 353 }
 354 
 355 /* pbinomfast may be wrong, while the simpler pbinom seems correct */
 356 define pbinomfast(x, n, p) {
 357     auto a, b, d, q, k, t;
 358     if ((p < 0) || (p > 1)) return (0);
 359     if (x < 0) return (0);
 360     if (x >= n) return (1);
 361 
 362     a = 1;
 363     q = 1 - p;
 364     b = b ^ n;
 365     d = 1;
 366     t = 0;
 367     for (k = 0; k < x;) {
 368         t += (per(n, k) / d) * a * b;
 369         a *= p;
 370         b /= q;
 371         k++;
 372         d *= k;
 373     }
 374     /* remember the last loop, where k == x */
 375     t += (per(n, k) / d) * a * b;
 376     return (t);
 377 }
 378 
 379 define ppois(x, l) {
 380     auto t, d, i;
 381     t = 1;
 382     d = 1;
 383     for (i = 1; i <= l; i++) {
 384         d *= i;
 385         t += (l ^ i) / d;
 386     }
 387     return (e(-l) * t);
 388 }
 389 
 390 define round(x, d) {
 391     auto k;
 392     k = 10 ^ d;
 393     return (round0(x * k) / k);
 394 }
 395 
 396 define round0(x) {
 397     auto i;
 398     i = x - mod(x, 1);
 399     if (x - i >= 0.5) return (i + 1);
 400     return (i);
 401 }
 402 
 403 define sign(x) {
 404     if (x > 0) return (1);
 405     if (x < 0) return (-1);
 406     return (0);
 407 }
 408 
 409 define tricube(x) {
 410     auto a, b, c, d;
 411     if ((x < -1) || (x > 1)) return (0);
 412     if (x >= 0) a = x else a = -x;
 413     b = a * a * a;
 414     c = 1 - b;
 415     d = c * c * c;
 416     return (70 / 81 * d);
 417 }
 418 '
 419 
 420 # `sed` code to adapt `ca` expressions into valid `bc` expressions
 421 fix_code='s-^+--g; s-_--g; s-\*\*-^-g; s-\[-(-g; s-\]-)-g'
 422 
 423 # `sed` code to ensure the results never start with a decimal dot, and to
 424 # rid the result of any trailing zero decimals and/or trailing decimal dots
 425 clean_trails='s-^\.-0.-; s/^-\./-0./; s-(\.[0-9]*[1-9])0+$-\1-; s-\.0*$--'
 426 
 427 # `bc` code with all the extra settings and definitions, which comes before
 428 # each expression to evaluate
 429 prelude="$(printf "scale = %s;\n\n%s\n" "${scale}" "${src}")"
 430 
 431 # ensure each output is all on 1 line, define several funcs and values, then
 432 # inject the expressions given as this script's arguments, transforming them
 433 # according to the rules described above
 434 for arg in "$@"; do
 435     [ "${verbose}" -eq 1 ] && printf "\033[7m%s\033[0m\n" "${arg}" >&2
 436 
 437     printf "%s\n%s\n" "${prelude}" "$(echo "${arg}" | sed "${fix_code}")" \
 438     | BC_LINE_LENGTH=0 bc -l \
 439     | sed -E "${clean_trails}"
 440 done