File: ca.sh
   1 #!/bin/sh
   2 
   3 # The MIT License (MIT)
   4 #
   5 # Copyright © 2025 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 [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` reads it from 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 and functions
  39 
  40 
  41 case "$1" in
  42     -h|--h|-help|--help)
  43         awk '/^# +ca /, /^$/ { gsub(/^# ?/, ""); print }' "$0"
  44         exit 0
  45     ;;
  46 esac
  47 
  48 [ "$1" = "--" ] && shift
  49 
  50 if [ $# -eq 0 ]; then
  51     awk '/^# +ca /, /^$/ { gsub(/^# ?/, ""); print }' "$0"
  52     exit 0
  53 fi
  54 
  55 # default max-accuracy decimals to use for calculations
  56 scale=25
  57 
  58 # ensure each output is all on 1 line, define several funcs and values, then
  59 # inject the expressions given as this script's arguments, transforming them
  60 # according to the rules described above
  61 for arg in "$@"; do
  62     # printf "\e[7m%s\e[0m\n" "${arg}" > /dev/stderr
  63     [ $# -ge 2 ] && printf "\e[7m%s\e[0m\n" "${arg}" > /dev/stderr
  64 
  65     BC_LINE_LENGTH=0 bc -l << ENDOFSCRIPT
  66 scale = ${scale};
  67 
  68 femto = 0.000000000000001;
  69 pico = 0.000000000001;
  70 nano = 0.000000001;
  71 micro = 0.000001;
  72 milli = 0.001;
  73 
  74 kilo = 1000;
  75 mega = 1000 * kilo;
  76 giga = 1000 * mega;
  77 tera = 1000 * giga;
  78 peta = 1000 * tera;
  79 exa =  1000 * peta;
  80 zetta =  1000 * exa;
  81 
  82 binkilo = 1024;
  83 binmega = 1024 * binkilo;
  84 bingiga = 1024 * binmega;
  85 bintera = 1024 * bingiga;
  86 binpeta = 1024 * bintera;
  87 binexa = 1024 * binpeta;
  88 binzetta = 1024 * binexa;
  89 
  90 kb = 1024;
  91 mb = 1024 * kb;
  92 gb = 1024 * mb;
  93 tb = 1024 * gb;
  94 pb = 1024 * tb;
  95 eb = 1024 * pb;
  96 zb = 1024 * eb;
  97 
  98 kib = 1024;
  99 mib = 1024 * kib;
 100 gib = 1024 * mib;
 101 tib = 1024 * gib;
 102 pib = 1024 * tib;
 103 zib = 1024 * pib;
 104 
 105 mol = 602214076000000000000000;
 106 mole = 602214076000000000000000;
 107 
 108 cup = 0.23658824;
 109 cup2l = 0.23658824;
 110 floz2l = 0.0295735295625;
 111 floz2ml = 29.5735295625;
 112 ft = 0.3048;
 113 ft2m = 0.3048;
 114 gal = 3.785411784;
 115 gal2l = 3.785411784;
 116 in = 2.54;
 117 in2cm = 2.54;
 118 lb = 0.45359237;
 119 lb2kg = 0.45359237;
 120 mi = 1.609344;
 121 mi2km = 1.609344;
 122 mpg = 0.425143707;
 123 mpg2kpl = 0.425143707;
 124 nm = 1.852;
 125 nm2km = 1.852;
 126 nmi = 1.852;
 127 nmi2km = 1.852;
 128 oz2g = 28.349523125
 129 psi2pa = 6894.757293168;
 130 ton = 907.18474;
 131 ton2kg = 907.18474;
 132 yd = 0.9144;
 133 yd2m = 0.9144;
 134 
 135 ga2l = gal2l;
 136 nm2km = nmi2km;
 137 tn2kg = ton2kg;
 138 
 139 million = 1000000
 140 billion = 1000 * million
 141 trillion = 1000 * billion
 142 
 143 hour = 3600;
 144 day = 24 * hour;
 145 week = 7 * day;
 146 
 147 hr = hour;
 148 wk = week;
 149 
 150 define ftin(f, i) {
 151     return (0.3048 * f + 0.0254 * i);
 152 }
 153 
 154 define lboz(l, o) {
 155     return (0.45359237 * l + 0.028349523 * o);
 156 }
 157 
 158 define bits(x) {
 159     return (log2(x));
 160 }
 161 
 162 define digits(x) {
 163     return (log10(x));
 164 }
 165 
 166 define eu() {
 167     return (e(1));
 168 }
 169 
 170 define euler() {
 171     return (e(1));
 172 }
 173 
 174 define pi() {
 175     return (4 * a(1));
 176 }
 177 
 178 define tau() {
 179     return (8 * a(1));
 180 }
 181 
 182 define deg(x) {
 183     return (180 * x / pi());
 184 }
 185 
 186 define rad(x) {
 187     return (pi() * x / 180);
 188 }
 189 
 190 define degrees(x) {
 191     return (deg(x));
 192 }
 193 
 194 define radians(x) {
 195     return (rad(x));
 196 }
 197 
 198 define abs(x) {
 199     if (x >= 0) return (x);
 200     return (-x);
 201 }
 202 
 203 define exp(x) {
 204     return (e(x));
 205 }
 206 
 207 define j0(x) {
 208     return (j(0, x));
 209 }
 210 
 211 define j1(x) {
 212     return (j(1, x));
 213 }
 214 
 215 define ln(x) {
 216     return (l(x));
 217 }
 218 
 219 define log(x) {
 220     return (l(x));
 221 }
 222 
 223 define log2(x) {
 224     auto r, n;
 225     if (x <= 0) return (l(x) / l(2));
 226 
 227     r = 0;
 228     for (n = x; n > 1; n /= 2) r += 1;
 229 
 230     if (n == 1) return (r);
 231     return (l(x) / l(2));
 232 }
 233 
 234 define log10(x) {
 235     auto r, n;
 236     if (x <= 0) return (l(x) / l(10));
 237 
 238     r = 0;
 239     for (n = x; n > 1; n /= 10) r += 1;
 240 
 241     if (n == 1) return (r);
 242     return (l(x) / l(10));
 243 }
 244 
 245 define sin(x) {
 246     return (s(x));
 247 }
 248 
 249 define cos(x) {
 250     return (c(x));
 251 }
 252 
 253 define tan(x) {
 254     return (s(x) / c(x));
 255 }
 256 
 257 define cot(x) {
 258     return (c(x) / s(x));
 259 }
 260 
 261 define atan(x) {
 262     return (a(x));
 263 }
 264 
 265 define sinh(x) {
 266     return ((e(x) - e(-x)) / 2);
 267 }
 268 
 269 define cosh(x) {
 270     return ((e(x) + e(-x)) / 2);
 271 }
 272 
 273 define tanh(x) {
 274     return ((e(x) - e(-x)) / (e(x) + e(-x)));
 275 }
 276 
 277 define coth(x) {
 278     return ((e(x) + e(-x)) / (e(x) - e(-x)));
 279 }
 280 
 281 define hypot(x, y) {
 282     return (sqrt(x*x + y*y));
 283 }
 284 
 285 define sinc(x) {
 286     if (x == 0) return (1);
 287     return (s(x) / x);
 288 }
 289 
 290 define min(x, y) {
 291     if (x <= y) return (x);
 292     return (y);
 293 }
 294 
 295 define max(x, y) {
 296     if (x >= y) return (x);
 297     return (y);
 298 }
 299 
 300 define mod(x, y) {
 301     auto s, m;
 302     s = scale;
 303     scale = 0;
 304     m = x % y;
 305     scale = s;
 306     return (m);
 307 }
 308 
 309 define mod1(x) {
 310     return (mod(x, 1));
 311 }
 312 
 313 define modf(x) {
 314     return (mod(x, 1));
 315 }
 316 
 317 define mix(x, y, k) {
 318     return (x * (1 - k) + y * k);
 319 }
 320 
 321 define round0(x) {
 322     auto i;
 323     i = x - mod(x, 1);
 324     if (x - i >= 0.5) {
 325         return (i + 1);
 326     }
 327     return (i);
 328 }
 329 
 330 define round(x, d) {
 331     auto k;
 332     k = 10 ^ d;
 333     return (round0(x * k) / k);
 334 }
 335 
 336 define r(x, d) {
 337     return (round(x, d));
 338 }
 339 
 340 define r0(x) {
 341     return (round0(x));
 342 }
 343 
 344 define epa(x) {
 345     return (epanechnikov(x));
 346 }
 347 
 348 define epanechnikov(x) {
 349     if ((x < -1) || (x > 1)) return (0);
 350     return (3 / 4 * (1 - (x * x)));
 351 }
 352 
 353 define gauss(x) {
 354     return (gaussian(x));
 355 }
 356 
 357 define gaussian(x) {
 358     return (e(-(x * x)));
 359 }
 360 
 361 define tricube(x) {
 362     auto a, b, c, d;
 363     if ((x < -1) || (x > 1)) return (0);
 364     if (x >= 0) a = x else a = -x;
 365     b = a * a * a;
 366     c = 1 - b;
 367     d = c * c * c;
 368     return (70 / 81 * d);
 369 }
 370 
 371 define fac(x) {
 372     auto f, i;
 373     if (x < 0) return (0);
 374     f = 1;
 375     for (i = x; i >= 2; i--) f *= i;
 376     return (f);
 377 }
 378 
 379 define f(x) {
 380     return (fac(x));
 381 }
 382 
 383 define fact(x) {
 384     return (fac(x));
 385 }
 386 
 387 define factorial(x) {
 388     return (fac(x));
 389 }
 390 
 391 define per(n, k) {
 392     auto p, i;
 393     if (n < k) return (0);
 394     p = 1;
 395     for (i = n; i >= n - k + 1; i--) p *= i;
 396     return (p);
 397 }
 398 
 399 define p(n, k) {
 400     return (per(n, k));
 401 }
 402 
 403 define perm(n, k) {
 404     return (per(n, k));
 405 }
 406 
 407 define permut(n, k) {
 408     return (per(n, k));
 409 }
 410 
 411 define permutations(n, k) {
 412     return (per(n, k));
 413 }
 414 
 415 /* the name "c" is already used for the cosine function in "bc" */
 416 
 417 define choose(n, k) {
 418     return (com(n, k));
 419 }
 420 
 421 define com(n, k) {
 422     if (n < k) return (0);
 423     return (per(n, k) / fac(k));
 424 }
 425 
 426 define comb(n, k) {
 427     return (com(n, k));
 428 }
 429 
 430 define combin(n, k) {
 431     return (com(n, k));
 432 }
 433 
 434 define combinations(n, k) {
 435     return (com(n, k));
 436 }
 437 
 438 define gcd(x, y) {
 439     return (x * y / lcm(x, y));
 440 }
 441 
 442 define lcm(x, y) {
 443     auto a, b, z;
 444 
 445     /* the LCM is defined only for positive integers */
 446     /* if (mod(x, 1) != 0 || x < 1 || mod(y, 1) != 0 || y < 1) return (0); */
 447     /* if (mod(x, 1) != 0) return (0); */
 448     if (x < 1) return (0);
 449     /* if (mod(y, 1) != 0) return (0); */
 450     if (y < 1) return (0);
 451 
 452     a = min(x, y);
 453     b = max(x, y);
 454 
 455     z = b;
 456     while (mod(z, a) != 0) { z += b; }
 457     return (z);
 458 }
 459 
 460 define dbin(x, n, p) {
 461     return (dbinom(x, n, p));
 462 }
 463 
 464 define pbin(x, n, p) {
 465     return (pbinom(x, n, p));
 466 }
 467 
 468 define dbinom(x, n, p) {
 469     return (com(n, x) * (p ^ x) * ((1 - p) ^ (n - x)));
 470 }
 471 
 472 /* pbinom inefficiently repeats calculations for now, which keeps it simple */
 473 define pbinom(x, n, p) {
 474     auto k, t;
 475     t = 0;
 476     for (k = 0; k <= n; k++) t += dbinom(k, n, p);
 477     return (t);
 478 }
 479 
 480 /* pbinomfast may be wrong, while the simpler pbinom seems correct */
 481 define pbinomfast(x, n, p) {
 482     auto a, b, d, q, k, t;
 483     if ((p < 0) || (p > 1)) return (0);
 484     if (x < 0) return (0);
 485     if (x >= n) return (1);
 486     a = 1;
 487     q = 1 - p;
 488     b = b ^ n;
 489     d = 1;
 490     t = 0;
 491     for (k = 0; k < x;) {
 492         t += (per(n, k) / d) * a * b;
 493         a *= p;
 494         b /= q;
 495         k++;
 496         d *= k;
 497     }
 498     /* remember the last loop, where k == x */
 499     t += (per(n, k) / d) * a * b;
 500     return (t);
 501 }
 502 
 503 define dexp(x, r) {
 504     if (r < 0) return (0);
 505     return (r * e(-r * x));
 506 }
 507 
 508 define pexp(x, r) {
 509     if (r < 0) return (0);
 510     return (1 - e(-r * x));
 511 }
 512 
 513 define dpois(x, l) {
 514     return ((l ^ x) * e(-l) / fac(x));
 515 }
 516 
 517 define ppois(x, l) {
 518     auto t, d, i;
 519     t = 1;
 520     d = 1;
 521     for (i = 1; i <= l; i++) {
 522         d *= i;
 523         t += (l ^ i) / d;
 524     }
 525     return (e(-l) * t);
 526 }
 527 
 528 define sgn(x) {
 529     if (x > 0) return (1);
 530     if (x < 0) return (-1);
 531     return (0);
 532 }
 533 
 534 define sign(x) {
 535     return (sgn(x));
 536 }
 537 
 538 define logistic(x) {
 539     return (1 / (1 + e(-x)));
 540 }
 541 
 542 $(echo "${arg}" | sed 's-^+--g; s-_--g; s-\*\*-^-g; s-\[-(-g; s-\]-)-g')
 543 ENDOFSCRIPT
 544 
 545 done |
 546 # ensure the result shows at least a zero before the decimal dot, then
 547 # rid the result of trailing zero decimals and/or trailing decimal dots
 548 sed -E 's-^\.-0.-; s/^-\./-0./; s-(\.[0-9]*[1-9])0+$-\1-; s-\.0*$--'