# Writing a convenient Bash wrapper for the DC calculator For complicated calculations, I usually use Ruby. For example: in preparing my tax returns, one tedious, error-prone task is completing the qualified-dividends-and-capital-gain-tax worksheet: => tax-worksheet.png It doesn't need to be submitted, but I wish to keep it for my records, and the IRS's PDF for it isn't a fillable form. Instead of drawing on the worksheet with an image editor, I replicated the worksheet and my data in a script. Not only does that preserve my work, it takes only a moment to copy the previous year's version and update it: ```ruby #!/usr/bin/env ruby # From the IRS 1040 instructions for TY 2024, pp. 64-75. def tax(n) case n when 0 0 when 10_950..11_000 1_098 when 12_300..12_350 1_247 else raise "Unknown tax value for #{n}." end end # From the IRS 1040 instructions for TY 2024, p. 36. def qualified_gains_worksheet( taxable_income:, qualified_dividends:, capital_gain:, filing_schedule_d: ) # Taxable income from Form 1040 line 15. l1 = taxable_income # Qualified dividends from Form 1040 line 3a. l2 = qualified_dividends l3 = if filing_schedule_d raise else # Capital gain, from Form 1040 line 7. capital_gain end l4 = l2 + l3 l5 = [0, l1 - l4].max # For singles. l6 = 47_025 l7 = [l1, l6].min l8 = [l5, l7].min l9 = l7 - l8 l10 = [l1, l4].min l11 = l9 l12 = l10 - l11 # For singles. l13 = 518_900 l14 = [l1, l13].min l15 = l5 + l9 l16 = [0, l14 - l15].max l17 = [l12, l16].min l18 = l17 * 0.15 l19 = l9 + l17 l20 = l10 - l19 l21 = l20 * 0.2 l22 = tax(l5) l23 = (l18 + l21 + l22).round(2) l24 = tax(l1).round(2) l25 = [l23, l24].min printf " l23 = %.2f\n", l23 printf " l24 = %.2f\n", l24 printf " l25 = %.2f\n", l25 printf "Tax savings over straight computation: %.2f\n", l24.round(2) - l23.round(2) printf "Enter this value on Form 1040 line 16: %.2f\n", l25 end qualified_gains_worksheet( taxable_income: 12345.67, qualified_dividends: 1234.56, capital_gain: 123.45, filing_schedule_d: false ) ``` For one-off computations, I often use the stack-based reverse-Polish-notation "desk calculator" command-line utility DC. Suppose I wished to know the pace, in minutes per mile, I'd need to run to finish a 5K in 25 minutes: ``` $ dc -e "10k 5000 100*2.54/12/5280/ 25r/ 60*0k60~ rn[:]Pp" 8:2.8032000120 ``` Explanation: ```dc # Set precision to 10. 10k # Compute miles per 5K. 5000 100* 2.54/ 12/ 5280/ # Divide 25 minutes by the distance in miles. (The 'r' swaps the values before # division.) 25r/ # To convert fractional minutes to minutes and seconds, we convert minutes to # seconds and then divide by 60 to get a quotient in minutes and a remainder in # seconds: # Convert minutes to seconds. 60* # Set precision to 0 so the remainder of the following division will be # integral. 0k # Divide by 60, pushing the quotient (minutes) before the remainder (seconds). 60~ # Reverse minutes and seconds, and print `${minutes}:${seconds}`. rn[:]Pp c ``` The convenience of postfix notation is that later calculations don't require going backward. That is, given the calculation for pace in minutes per mile: ``` 10k 5000 100*2.54/12/5280/ 25r/ ``` we can easily convert to miles per hour by inverting (giving miles per minute) and multiplying by 60 (giving miles per hour): ``` 10k 5000 100*2.54/12/5280/ 25r/ 1r/60* ``` I use DC often enough that I years ago added this wrapper to my `.bashrc` to make a "z" command which uses its arguments as a DC script, first setting the precision to 10 and afterwards printing the stack: ```sh z() { dc -e "10k $* f"; } ``` This was convenient: ``` # i forgor how many tablespoons is quart?? $ z 2 8\* 4\* 64 # caitlin clark is 1.83 m tall. what's that in american? $ z 183 2.54/0k12\~ .0472440944 6 ``` It'd be nice if I didn't have to escape or to quote `*`, `~`, and `[` in Z's arguments. Quick review: Without escaping, these are subject to pathname and tilde expansion, so, the command: ```sh z 190 12 7*+ 0k8 ~ p ``` might expand to this nonsense: ```sh z 190 12 7.txt+ 0k8 /home/nepeta p ``` Bash has a "DEBUG" trap which executes before each command with the unexpanded command to be executed stored in `$BASH_COMMAND`. This lets me capture Z's unexpanded arguments and pass them to DC: ```sh _debug_trap() { _command=${BASH_COMMAND#z } } trap _debug_trap DEBUG z() { dc -e "10k ${_command#z } f" } ``` This works, but the expansion still happens, which offends my sense of purity and, depending on my working directory, could be prohibitively slow. Bash has another feature though: `set -f` disables pathname expansion, and setting it in a "DEBUG" trap disables it for the command which is about to be executed. So I can disable pathname expansion in my "DEBUG" trap when the command is `z` and reenable it in `z`. This doesn't prevent tilde expansion, but, on my systems, user lookup is fast. Adding some other niceties, I'm now using: ```sh # z.sh, sourced by ~/.bashrc declare _dc_command="" declare -i _dc_reenable_pathname_expansion=0 _debug_trap() { declare -g _dc_command _dc_reenable_pathname_expansion if [[ $BASH_COMMAND == "z "* ]]; then _dc_command=${BASH_COMMAND#z } # Don't disable and later reenable pathname expansion if it's already # disabled. if [[ $- != *f* ]]; then set -f _dc_reenable_pathname_expansion=1 fi fi } trap _debug_trap DEBUG z() { declare -g _dc_command _dc_reenable_pathname_expansion declare cmd # This allows this function to work without the DEBUG trap. if [[ -n $_dc_command ]]; then cmd=$_dc_command _dc_command="" else cmd=$* fi if ((_dc_reenable_pathname_expansion)); then set +f _dc_reenable_pathname_expansion=0 fi dc -e "10k $cmd f" } ``` Published November 2025. ## Navigation => index.gmi Site index => gemini://sdf.org/nepeta/clever-dc.gmi Gemini permalink for this page => http://nepeta.chaosnet.org/clever-dc.html HTTP permalink for this page