File: ca.sh
   1 #!/bin/sh
   2 
   3 # The MIT License (MIT)
   4 #
   5 # Copyright © 2024 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 [expression]
  27 #
  28 # CAlculator is an easier-to-use version of bc (basic calculator) where
  29 #
  30 #   - you give the expression as an argument, while bc reads it from stdin
  31 #   - you can use either ** or ^ to raise powers
  32 #   - you can use [ and ] or ( and ) interchangeably
  33 #   - the number of decimals is 6 by default
  34 #   - automatically includes the extended bc math library via option -l
  35 #   - there are several extra predefined values and functions (see below)
  36 #
  37 #
  38 # Predefined functions and values, besides the ones `bc` already comes with
  39 #
  40 #
  41 # nano   a billionth
  42 # micro  a millionth
  43 # milli  a thousandth
  44 #
  45 # kilo   1000
  46 # mega   a million
  47 # giga   a billion
  48 # tera   a thousand billion / trillion
  49 # peta   a million billion / quadrillion
  50 # exa    a quintillion
  51 # zetta  a sixtillion
  52 #
  53 # binkilo   1024
  54 # binmega   1024 * 1024
  55 # bingiga   1024 * 1024 * 1024
  56 # bintera   1024 * 1024 * 1024 * 1024
  57 # binpeta   1024 * 1024 * 1024 * 1024 * 1024
  58 # binexa    1024 * 1024 * 1024 * 1024 * 1024 * 1024
  59 # binzetta  1024 * 1024 * 1024 * 1024 * 1024 * 1024
  60 #
  61 # kb  1024
  62 # mb  1024 * 1024
  63 # gb  1024 * 1024 * 1024
  64 # tb  1024 * 1024 * 1024 * 1024
  65 # pb  1024 * 1024 * 1024 * 1024 * 1024
  66 #
  67 # kib  1024
  68 # mib  1024 * 1024
  69 # gib  1024 * 1024 * 1024
  70 # tib  1024 * 1024 * 1024 * 1024
  71 # pib  1024 * 1024 * 1024 * 1024 * 1024
  72 #
  73 # mol      1 mole, the exact avogadro number
  74 # mole     the exact avogadro number
  75 #
  76 # cup2l    liters in 1 cup
  77 # floz2ml  milliliters in 1 fluid ounce
  78 # gal2l    liters in 1 gallon
  79 # in2cm    centimeters in 1 inch
  80 # lb2kg    kilograms in 1 pound
  81 # mi2km    kilometers in 1 mile
  82 # mpg2kpl  kilometers per liter in 1 mile per gallon
  83 # nmi2km   kilometers in 1 nautical mile
  84 # oz2g     grams in 1 weight ounce
  85 # psi2pa   pascals in 1 pound per square-inch
  86 # ton2kg   kilograms in 1 american ton
  87 # yd2m     meters in 1 yard
  88 #
  89 # eu()          approximate Euler's number
  90 # pi()          approximate any circle's circumference/diameter ratio
  91 # tau()         2*pi; approximate any circle's circumference/radius ratio
  92 # abs(x)        the absolute value function
  93 # cbrt(x)       the cube root function
  94 # exp(x)        the exponential function; alias for bc's e
  95 # ln(x)         natural logarithm; an alias for bc's l
  96 # log(x)        natural logarithm; an alias for bc's l
  97 # log2(x)       base-2 logarithm
  98 # log10(x)      base-10 logarithm
  99 # sin(x)        the sine
 100 # cos(x)        the cosine
 101 # atan(x)       the arc-tangent
 102 # hypot(x, y)   length of the hypothenuse
 103 # mod(x, y)     modulus function, which is different from the % operator
 104 # round0(x)     round to integer
 105 # round1(x)     round to 1 decimal place
 106 # ...
 107 # round20(x)    round to 20 decimal places
 108 # round(x, d)   round to the given number of decimal digits; can be negative
 109 # fac(x)        factorial
 110 # com(n, k)     number of combinations
 111 # comb(n, k)    number of combinations
 112 # per(n, k)     number of permutations
 113 # perm(n, k)    number of permutations
 114 # lcm(x, y)     least-common multiple
 115 # gcd(x, y)     greatest-common divisor
 116 # min(x, y)     the minimum of 2 numbers
 117 # max(x, y)     the maximum of 2 numbers
 118 # isprime(x)    check if number is prime
 119 # lgamma(x)     lanczos approximation of the log-gamma function
 120 # lbeta(x, y)   lanczos approximation of the log-beta function
 121 # gamma(x)      approximation of the gamma function
 122 # beta(x)       approximation of the beta function
 123 # logistic(x)   the logistic function
 124 # mix(x, y, k)  interpolate 2 numbers; extrapolates when k is outside [0, 1]
 125 # clamp(x, min, max)  trap a number in the range given
 126 # wrap(x, min, max)  normalize a number in the range given
 127 
 128 
 129 # handle help options
 130 case "$1" in
 131     -h|--h|-help|--help)
 132         # show help message, extracting the info-comment at the start
 133         # of this file, and quit
 134         awk '/^# +ca/, /^$/ { gsub(/^# ?/, ""); print }' "$0"
 135         exit 0
 136     ;;
 137 esac
 138 
 139 (
 140 
 141 # ensure output is all on 1 line, define several funcs and values, then
 142 # inject the expression given as this script's arguments, transformed
 143 # according to the rules described above
 144 BC_LINE_LENGTH=0 bc -l << ENDOFSCRIPT
 145 scale = 20;
 146 
 147 femto = 0.000000000000001;
 148 pico = 0.000000000001;
 149 nano = 0.000000001;
 150 micro = 0.000001;
 151 milli = 0.001;
 152 
 153 kilo = 1000;
 154 mega = 1000 * kilo;
 155 giga = 1000 * mega;
 156 tera = 1000 * giga;
 157 peta = 1000 * tera;
 158 exa =  1000 * peta;
 159 zetta =  1000 * exa;
 160 
 161 binkilo = 1024;
 162 binmega = 1024 * binkilo;
 163 bingiga = 1024 * binmega;
 164 bintera = 1024 * bingiga;
 165 binpeta = 1024 * bintera;
 166 binexa = 1024 * binpeta;
 167 binzetta = 1024 * binexa;
 168 
 169 kb = 1024;
 170 mb = 1024 * kb;
 171 gb = 1024 * mb;
 172 tb = 1024 * gb;
 173 pb = 1024 * tb;
 174 eb = 1024 * pb;
 175 zb = 1024 * eb;
 176 
 177 kib = 1024;
 178 mib = 1024 * kib;
 179 gib = 1024 * mib;
 180 tib = 1024 * gib;
 181 pib = 1024 * tib;
 182 zib = 1024 * pib;
 183 
 184 mol = 602214076000000000000000;
 185 mole = 602214076000000000000000;
 186 
 187 cup2l = 0.23658824;
 188 floz2ml = 29.5735295625;
 189 ft2mt = 0.3048;
 190 gal2l = 3.785411784;
 191 in2cm = 2.54;
 192 lb2kg = 0.45359237;
 193 mi2km = 1.609344;
 194 mpg2kpl = 0.425143707;
 195 nmi2km = 1.852;
 196 oz2g = 28.349523125
 197 psi2pa = 6894.757293168;
 198 ton2kg = 907.18474;
 199 yd2m = 0.9144;
 200 
 201 ft2mt = ft2m;
 202 ga2l = gal2l;
 203 ga2lt = gal2l;
 204 gal2lt = gal2l;
 205 lb2k = lb2kg;
 206 nm2km = nmi2km;
 207 tn2kg = ton2kg;
 208 yd2mt = yd2m;
 209 
 210 hour = 3600;
 211 day = 24 * hour;
 212 week = 7 * day;
 213 
 214 hr = hour;
 215 wk = week;
 216 
 217 define eu() { return e(1); }
 218 define pi() { return 4*a(1); }
 219 define tau() { return 8*a(1); }
 220 
 221 define deg(x) { return 180 * x / pi(); }
 222 define rad(x) { return pi() * x / 180; }
 223 
 224 define abs(x) { if (x >= 0) return x else return -x; }
 225 define cbrt(x) { return x^(1/3); }
 226 define exp(x) { return e(x); }
 227 define ln(x) { return l(x); }
 228 # define log(base, x) { return l(x) / l(base); }
 229 define log(x) { return l(x); }
 230 define log2(x) { return l(x) / l(2); }
 231 define log10(x) { return l(x) / l(10); }
 232 
 233 define sin(x) { return s(x); }
 234 define cos(x) { return c(x); }
 235 define tan(x) { return s(x) / c(x); }
 236 define cot(x) { return c(x) / s(x); }
 237 define atan(x) { return a(x); }
 238 define sinh(x) { return (e(x) - e(-x)) / 2; }
 239 define cosh(x) { return (e(x) + e(-x)) / 2; }
 240 define tanh(x) { return (e(x) - e(-x)) / (e(x) + e(-x)); }
 241 define coth(x) { return (e(x) + e(-x)) / (e(x) - e(-x)); }
 242 define hypot(x, y) { return sqrt(x*x + y*y); }
 243 define sinc(x) { if (x == 0) return 1 else return s(x) / x; }
 244 
 245 define min(x, y) { if (x <= y) return x else return y; }
 246 define max(x, y) { if (x >= y) return x else return y; }
 247 
 248 define mod(x, y) {
 249     auto s, m;
 250     s = scale;
 251     scale = 0;
 252     m = x % y;
 253     scale = s;
 254     return m;
 255 }
 256 
 257 define round0(x) {
 258     auto i;
 259     i = x - mod(x, 1);
 260     if (x - i >= 0.5) {
 261         return i + 1;
 262     }
 263     return i;
 264 }
 265 
 266 define round(x, d) {
 267     auto k;
 268     k = 10^d;
 269     return round0(x * k) / k;
 270 }
 271 
 272 define round1(x) { return round(x, 1); }
 273 define round2(x) { return round(x, 2); }
 274 define round3(x) { return round(x, 3); }
 275 define round4(x) { return round(x, 4); }
 276 define round5(x) { return round(x, 5); }
 277 define round6(x) { return round(x, 6); }
 278 define round7(x) { return round(x, 7); }
 279 define round8(x) { return round(x, 8); }
 280 define round9(x) { return round(x, 9); }
 281 define round10(x) { return round(x, 10); }
 282 define round11(x) { return round(x, 11); }
 283 define round12(x) { return round(x, 12); }
 284 define round13(x) { return round(x, 13); }
 285 define round14(x) { return round(x, 14); }
 286 define round15(x) { return round(x, 15); }
 287 define round16(x) { return round(x, 16); }
 288 define round17(x) { return round(x, 17); }
 289 define round18(x) { return round(x, 18); }
 290 define round19(x) { return round(x, 19); }
 291 define round20(x) { return round(x, 20); }
 292 
 293 define fac(x) {
 294     auto y;
 295     if (x < 0) {
 296         return 0;
 297     }
 298     y = 1;
 299     for (i = 2; i <= x; i++) {
 300         y *= i;
 301     }
 302     return y;
 303 }
 304 
 305 define per(n, k) {
 306     auto p;
 307     if (n < k) {
 308         return 0;
 309     }
 310     p = 1;
 311     for (i = k + 1; i <= n; i++) {
 312         p *= i;
 313     }
 314     return p;
 315 }
 316 
 317 define perm(n, k) { return per(n, k); }
 318 
 319 define com(n, k) { if (n < k) return 0 else return per(n, k) / fac(k); }
 320 define comb(n, k) { return com(n, k); }
 321 define gcd(x, y) { return x * y / lcm(x, y); }
 322 
 323 define lcm(x, y) {
 324     auto a, b;
 325 
 326     /* the LCM is defined only for positive integers */
 327     if (mod(x, 1) != 0 || x < 1 || mod(y, 1) != 0 || y < 1) {
 328         return 0;
 329     }
 330 
 331     a = min(x, y);
 332     b = max(x, y);
 333 
 334     z = b;
 335     while (mod(z, a) != 0) {
 336         z += b;
 337     }
 338     return z;
 339 }
 340 
 341 define isprime(n) {
 342     auto div;
 343 
 344     if (mod(n, 1) != 0 || n < 2) {
 345         return 0;
 346     }
 347 
 348     /* 2 is the only even prime number */
 349     if (mod(n, 2) == 0) {
 350         return n == 2;
 351     }
 352 
 353     /*
 354         even numbers have already been handled, so only odd numbers
 355         make it here: only check up to the square-root, so the loop
 356         has O(n**1.5) time-complexity
 357     */
 358     for (div = 3; div <= sqrt(n); div += 2) {
 359         if (mod(n, div) == 0) {
 360             return 0;
 361         }
 362     }
 363     return 1;
 364 }
 365 
 366 /*
 367 I adapted an implementation of Lanczos' log-gamma approximation I found at
 368 http://introcs.cs.princeton.edu/java/91float/Gamma.java.html
 369 */
 370 define lgamma(x) {
 371     auto tmp, a, b, c, d;
 372     tmp = (x - 0.5) * l(x + 4.5) - (x + 4.5);
 373     a = 76.18009173 / (x + 0) - 86.50532033 / (x + 1);
 374     b = 24.01409822 / (x + 2) - 1.231739516 / (x + 3);
 375     c = 0.00120858003 / (x + 4) - 0.00000536382 / (x + 5);
 376     d = sqrt(2 * 3.14159265358979323844);
 377     #d = sqrt(2 * pi());
 378     return tmp + l((1.0 + a + b + c) * d);
 379 }
 380 
 381 define lbeta(x, y) { return lgamma(x) + lgamma(y) - lgamma(x + y); }
 382 
 383 define gamma(x) { return e(lgamma(x)); }
 384 define beta(x, y) { return e(lbeta(x, y)); }
 385 define logistic(x) { return 1 / (1 + e(-x)); }
 386 
 387 define mix(x, y, k) { return x + (1 - k) * (y - x); }
 388 define clamp(x, min, max) { return max(min(max, x), min); }
 389 define wrap(x, min, max) { return (x - min) / (max - min); }
 390 define unwrap(x, min, max) { return (max - min) * x + min; }
 391 
 392 $(echo "$@" | sed 's-^+--g; s-_--g; s-\*\*-^-g; s-\[-(-g; s-\]-)-g')
 393 ENDOFSCRIPT
 394 
 395 # ensure the result shows at least a zero before the decimal dot, then
 396 # rid the result of trailing zero decimals and/or trailing decimal dots
 397 ) | sed -E 's-^\.-0.-; s/^-\./-0./; s-(\.[0-9]+[1-9]+)0+$-\1-; s-\.0*$--'