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