#!/usr/bin/awk -f #------------------------------------------------------------------------------- # rechner.awk (C) 2004-2007 T.Birnthaler OSTC GmbH # $Id: rechner.awk,v 1.19 2009-03-07 10:34:30 tsbirn Exp #------------------------------------------------------------------------------- # Auf Kommandozeile angegebene Argumente als Formel(n) betrachten/ausrechnen. # - Es ergibt sich eine Zahlenliste mit 1 bis N Elementen. # - Standardmaessig jede Zahl um die Mehrwertsteuer erhoehen (Std: 19%) und als # - Summe = Betrag + Mehrwertsteuer mit 2 Nachkommastellen ausgeben. # - Bei mehr als einer Zahl das Total + MwSt ebenfalls ausgeben. # Alternativ DM <-> Euro konvertieren (Optionen -d/-e bzw. Programmname) # - euro2dm e2d d # - dm2euro d2e e # Alternativ keine MwSt + Konvertierung (Optionen -t bzw. Programmname) # - calc t #------------------------------------------------------------------------------- # Operatoren (Vorrang von oben nach unten abnehmend): # ( [ { a b Klammer auf # ) ] } e z Klammer zu # ** xx ^ % § ~ Potenzierung/Prozentrechung # * x / : _ Multiplikation/Division/Modulo # + - Addition/Subtraktion # Zahlen mit Dezimal-"." und "," schreibbar # 1.23 # 3,45 #------------------------------------------------------------------------------- # % = Prozentualer Anteil: WERT % PROZ -> WERT * PROZ / 100 # § ~ = Prozentualer Abschlag: WERT ° PROZ -> WERT * 100 / (100 + PROZ) # ° = Prozentualer Unterschied: WERT § BASIS -> 100 * (WERT - BASIS) / BASIS #------------------------------------------------------------------------------- # Formeln der Form # # ((1 + 2) x 3 ^ (4 - 5) * 6) % 7 ** 8 / 9 (x ~ *, ** ~ ^, / ~ :) # # werden folgendermassen etwas trickreich ausgewertet: Klammern von "innen" nach # "aussen" ausrechnen und Zahlenpaare mit Operator dazwischen nach absteigendem # Vorrang (Exponent vor Punkt vor Strich) auswerten. Den jeweiligen Formelteil # dann durch sein Ergebnis + leere Argumente statt des Formelteils ersetzen. # Nach jedem dieser Schritte leere Formelteile durch Melt() entfernen. # Man haette das Ganze natuerlich auch per Parser + Scanner machen koennen, # aber so funktioniert's auch ganz gut (das automatische Summieren mehrerer # Zahlen waere damit vielleicht doch nicht so einfach gewesen?). #------------------------------------------------------------------------------- # TODO # * Schreibweise 6% akzeptieren # * Exponentialschreibweise akzeptieren # * Weitere Operatoren? # * Zins-Berechnungen? # * Schalter "-m" wird auch vom gawk interpretiert -> Usage-Meldung # DONE # * Tausendertrennzeichen akzeptieren (egal ob "." oder ",", letztes # Trennzeichen ist Dezimaltrenner, vorherige Zeichen sind Tausendertrenner) # * Schalter -n: Runterrechnen der MwSt statt Raufrechnen (Brutto -> Netto) # * Runden per Schalter -r # * Prozentualer Anteil: WERT % PROZENT -> WERT / 100 x PROZENT # * Steigerung in Prozent: NEU § ALT -> (NEU - ALT) / ALT * 100 # * Option -n = MwSt abziehen (Netto) # * Optionen -d/e fuer DM/Euro-Umwandlung # * Syntaxfehler (doppelte Operatoren, ...) vor Auswertung entfernen # * Nur reduzieren wenn notwendig # * Option -g = Debug # * Leerraum zwischen Formelelemente darf fehlen # * Formel(teile) als Textstring angebbar (ganze Formel quotieren) # * Option -m = MwSt-Satz # * Klammern erlauben # * Operatoren standardisieren # * 1. Schritt: Reduktion der Argumente + Indexverschiebung # Argumente: mwst.awk -t 2 + 3 -> 2 + 3 # ARGV[.]: 0 1 2 3 4 -> 1 2 4 # ARGC: 5 -> 3 # * LANG=C LC_ALL=C notwendig, sonst Kommazahlen nicht erkannt (egal "." / ",") # * Kommastellen mit "," und "." erlauben # * Programmname "calc" + "t" oder Option "-t" -> Rechnen OHNE Mehrwertsteuer #------------------------------------------------------------------------------- # {([ BRACE MATCHING HACK #------------------------------------------------------------------------------- BEGIN { DBG = 0 # Debugging aus MWST = 16 # 16% MwSt bis 31.12.2006 MWST = 19 # 19% MwSt ab 1.1.2007 FAKTOR = 0 # Umrechnungsfaktor EURO2DM = 1.95583 # Umrechnungsfaktor Euro -> DM DM2EURO = 1/EURO2DM # Umrechnungsfaktor DM -> Euro ROUND = -1 # Runden auf N Nachkommastellen (-1 = nicht!) ENVIRON["LANG"] = "C" # Leider zu spaet, muss AUSSEN erfolgen! ENVIRON["LC_ALL"] = "C" # Leider zu spaet, muss AUSSEN erfolgen! --ARGC # Trick: Formel-Argumente stehen in ARGV[1..ARGC] PGM = ENVIRON["_"] # Programmname (mit Pfad) sub(/.*[\/\\]/, "", PGM) # Reiner Programmname (Pfad entfernen) # Programmname "t" oder "calc"? -> Keine Mehrwertsteuer aufschlagen if (PGM ~ /^t$/ || PGM ~ /^calc$/) { FAKTOR = 0 MWST = 0 } # Programmname "d", "d2e" oder "euro2dm"? -> Umrechnung Euro -> DM else if (PGM ~ /^d$/ || PGM ~ /^e2d$/ || PGM ~ /^euro2dm$/) { FAKTOR = EURO2DM MWST = 0 } # Programmname "e", "e2d" oder "dm2euro"? -> Umrechnung DM -> Euro else if (PGM ~ /^e$/ || PGM ~ /^d2e$/ || PGM ~ /^dm2euro$/) { FAKTOR = DM2EURO MWST = 0 } # Hauptprogramm if (ARGC <= 1) Usage("") ARGC = Opts(ARGC) # Optionen erkennen if (DBG) print "FAKTOR =", FAKTOR if (DBG) print "MWST =", MWST if (DBG) DumpArgs("ARGS", 1, ARGC) ARGC = Std(ARGC) # Formel(n) standardisieren ARGC = Calc(ARGC) # Formel(n) ausrechnen Print(ARGC) # Ergebnis(se) ausgeben exit 0 } #------------------------------------------------------------------------------- # Usage-Meldung #------------------------------------------------------------------------------- function Usage(errmsg) { if (errmsg) print errmsg print "usage: rechner.awk[OPTIONEN] FORMEL(N)" print " Mehrwertsteuer aufschlagen (oder abziehen) oder" print " Umrechnung Euro <-> DM oder einfacher Taschenrechner." print " Operatoren (Vorrang von oben nach unten abnehmend):" print " ( [ { a b Klammer auf" print " ) ] } e z Klammer zu" print " ** xx ^ Potenzierung" print " % § ~ Prozentrechung" print " * x / : _ Multiplikation/Division/Modulo" print " + - Addition/Subtraktion" print " Zahlen mit Dezimal-'.' und ',' schreibbar:" print " 1 123 1.23 3,141592" print " Optionen:" print " -d Umrechnung Euro -> DM (" EURO2DM ")" print " -e Umrechnung DM -> Euro (" 1/EURO2DM ")" print " -g Debugging an" print " -h/-? Usage-Meldung" print " -m MWST Mehrwertsteuer-Satz (Std: " MWST "%)" print " -n Mehrwertsteuer abziehen (Netto)" print " -o Oktale Ausgabe" print " -r N Runden auf N Nachkommstellen" print " -t Taschenrechner (ohne MwSt, Umrechnung, Runden)" print " -T Total ermitteln (auch wenn nur ein Wert)" print " -x Hexadezimale Ausgabe" exit 1 } #------------------------------------------------------------------------------- # Optionen erkennen #------------------------------------------------------------------------------- function Opts(argc, dbg) { while (ARGV[1] ~ /^-/) { # Umrechnung Euro -> DM, keine Mehrwersteuer if (ARGV[1] == "-d") { FAKTOR = EURO2DM MWST = 0 ARGV[1] = "" } # Umrechnung DM -> Euro, keine Mehrwersteuer else if (ARGV[1] == "-e") { FAKTOR = DM2EURO MWST = 0 ARGV[1] = "" } # Debugging an else if (ARGV[1] == "-g") { dbg = 1 ARGV[1] = "" } # Usage-Meldung else if (ARGV[1] == "-h" || ARGV[1] == "-?") { Usage() } # Mehrwertsteuersatz, keine Umrechnung else if (ARGV[1] == "-m") { FAKTOR = 0 MWST = ARGV[2] ARGV[1] = "" ARGV[2] = "" } # Mehrwertsteuersatz abziehen (Netto), keine Umrechnung else if (ARGV[1] == "-n") { FAKTOR = 0 MWST = -MWST ARGV[1] = "" } # Oktale Ausgabe = keiner Mehrwertsteuer, keine Umrechnung else if (ARGV[1] == "-o") { MWST = 0 FAKTOR = 0 OCTAL = 1 ARGV[1] = "" } # Runden auf N Nachkommstellen else if (ARGV[1] == "-r") { ROUND = int(ARGV[2]) ARGV[1] = "" ARGV[2] = "" } # Taschenrechner = keine Mehrwertsteuer, keine Umrechnung else if (ARGV[1] == "-t") { MWST = 0 FAKTOR = 0 ARGV[1] = "" } # Mehr als ein Argument uebrig? -> Total ermitteln else if (ARGV[1] == "-T") { TOTAL = 1; ARGV[1] = "" } # Hexadezimales Ausgabe = keiner Mehrwertsteuer, keine Umrechnung else if (ARGV[1] == "-x") { MWST = 0 FAKTOR = 0 HEXA = 1 ARGV[1] = "" } else { Usage("error: unknown option " ARGV[1]) } argc = Melt(1, argc) } if (dbg) DBG=1 # kleiner Trick, damit beim Optionen parsen keine Meldung if (DBG) DumpArgs("OPTS", 1, argc) return argc } #------------------------------------------------------------------------------- # Alle Argumente durch Whitespace getrennt aneinanderhaengen # und Operatoren standardisieren # ** -> ^ # Potenzierung # x -> * # Multiplikation # : -> / # Division # _ -> _ # Modulo # {[ ( # a=auf, b=begin # }]>ez -> ) # z=zu, e=end(3) # und Kommazahlen mit "," in "." umwandeln # und Ergebnis an Whitespaces wieder aufteilen. #------------------------------------------------------------------------------- function Std(argc, formula) { if (DBG) DumpArgs("IN", 1, argc) formula = " " for (i = 1; i <= argc; ++i) formula = formula ARGV[i] " " if (DBG) print "FORMULA1:", formula # Hilfsoperatoren ersetzen gsub(/\*\*/, "^", formula) # ** -> ^ gsub(/xx/, "^", formula) # xx -> ^ gsub(/x/, "*", formula) # x -> * gsub(/:/, "/", formula) # : -> / gsub(/[\[{ ( gsub(/[\]}>ez]/, ")", formula) # ]}>ez -> ) gsub(/~/, "§", formula) # ~ -> § gsub(/[-+\/*%§^()]/, " & ", formula) # Leerraum um Operatoren setzen gsub(/[\t ]+/, " ", formula) # Leerraum -> 1 Leerzeichen gsub(/[\t ]+/, " ", formula) # Leerraum -> 1 Leerzeichen sub(/ +$/, "", formula) # Leerzeichen am Ende entfernen if (DBG) print "FORMULA2:", formula # Trick: Korrekturen bei mehreren Operatoren hintereinander (NICHT gsub!) # 6 Operatoren + 2 Klammern = 8^2 = 64 Kombinationen while (formula ~ /[-+*\/%§^(] [-+*\/%§^)]/) { if (DBG) print("REDUCE: |" formula "|") if (sub(/ \( \) /, " ", formula)) continue # ( ) -> Nix! if (sub(/\+ \+/, "+", formula)) continue # + + -> + if (sub(/- -/, "+", formula)) continue # - - -> + if (sub(/\+ -/, "-", formula)) continue # + - -> - if (sub(/- \+/, "-", formula)) continue # - + -> - if (sub(/\* \+/, "*", formula)) continue # * + -> * if (sub(/\/ \+/, "/", formula)) continue # / + -> / if (sub(/% \+/, "%", formula)) continue # % + -> % if (sub(/§ \+/, "§", formula)) continue # § + -> § if (sub(/\^ \+/, "^", formula)) continue # ^ + -> ^ if (sub(/\* \*/, "^", formula)) continue # * * -> ^ if (sub(/\/ \//, "/", formula)) continue # / / -> / if (sub(/% %/, "%", formula)) continue # % % -> % if (sub(/§ §/, "§", formula)) continue # § § -> § if (sub(/\^ \^/, "^", formula)) continue # ^ ^ -> ^ if (sub(/\( -/, "( 0 -", formula)) continue # ( - -> ( 0 - if (sub(/\( [+*\/%§^]/, "(", formula)) continue # ( Op -> ( if (sub(/[-+*\/%§^] \)/, ")", formula)) continue # Op ) -> ) if (sub(/ \* \/ /, " ", formula)) continue # * / -> Nix! if (sub(/ \/ \* /, " ", formula)) continue # / * -> Nix! sub(/ [-+*\/%§^] [-+*\/%§^] /, " ", formula) # Op Op -> Nix! } # )) if (DBG) print "FORMULA3:", formula argc = split(formula, ARGV) # In den Zahlen letztes ",." durch "." ersetzen, andere ",." entfernen for (i = 1; i <= argc; ++i) { if (! match(ARGV[i], /[0-9]/) || ! match(ARGV[i], /[.,]/)) continue cnt = split(ARGV[i], digits, /[,.]/) ARGV[i] = digits[1] for (j = 2; j < cnt; ++j) ARGV[i] = ARGV[i] digits[j] ARGV[i] = ARGV[i] "." digits[cnt] # ARGV[i] = ARGV[i] "," digits[cnt] } if (DBG) print "FORMULA4:", formula if (DBG) DumpArgs("OUT", 1, argc) return argc } #------------------------------------------------------------------------------- # Gesamtformel(n) mit Klammern ausrechnen und # bei mehr als einem Wert Total hinzufuegen. #------------------------------------------------------------------------------- function Calc(argc) { if (DBG) DumpArgs("CALC", 1, argc) # Klammern von innen nach aussen reduzieren und Klammern-Inneres ausrechnen while ((parpos = SubCalc(1, argc)) !~ /^ *$/) { split(parpos, pararr) Part(pararr[1], pararr[2]) argc = Melt(1, argc) } # Gesamt-Formel ausrechnen argc = Part(1, argc) # Mehr als ein Argument uebrig? -> Total ermitteln if (argc > 1 || TOTAL) { ++argc for (i = 1; i < argc; ++i) ARGV[argc] += ARGV[i] } return argc } #------------------------------------------------------------------------------- # Jede Zahl ausgeben + evtl. # MwSt aufschlagen/abziehen oder Umrechnung EURO <-> DM durchfuehren. #------------------------------------------------------------------------------- function Print(argc) { for (i = 1; i <= argc; ++i) { # Brutto = Netto + MwSt if (MWST > 0) { printf("%.2f = %.2f + %.2f MwSt\n", ARGV[i] * (100 + MWST) / 100, ARGV[i], ARGV[i] * MWST / 100) } # Netto = Brutto - MwSt else if (MWST < 0) { printf("%.2f = %.2f - %.2f MwSt\n", ARGV[i], ARGV[i] * 100 / (100 + -MWST), ARGV[i] * -MWST / (100 + -MWST)) } else if (FAKTOR == EURO2DM) { printf("%.2f Euro -> %.2f DM\n", ARGV[i], ARGV[i] * EURO2DM) } else if (FAKTOR == DM2EURO) { printf("%.2f DM -> %.2f Euro\n", ARGV[i], ARGV[i] / EURO2DM) } else if (OCTAL > 0) printf("%o\n", ARGV[i]) else if (HEXA > 0) printf("%x\n", ARGV[i]) else if (ROUND >= 0) printf("%." ROUND "f\n", ARGV[i]) else printf("%.2f\n", ARGV[i]) } } #------------------------------------------------------------------------------- # Teilformel ohne Klammern gemaess Vorrang auswerten: # * zuerst Potenzieren/Prozent (^ % §), rechts -> links, Ergebnis links # * dann Multiplikation/Division (* /), links -> rechts, Ergebnis rechts # * dann Addition/Subtraktion (+ -), links -> rechts, Ergebnis rechts #------------------------------------------------------------------------------- function Part(left, right, todo) { if (DBG) DumpArgs("PART", left, right) # Zuerst Potenzieren und Prozentrechung (rechts -> links, Ergebnis links) melt = 0 for (i = right-1; i > left; --i) { # Potenzierung if (ARGV[i] == "^") { ARGV[i-1] ^= ARGV[i+1] ARGV[i] = "" ARGV[i+1] = "" melt = 1 } # % = Prozentualer Anteil: WERT % PROZ -> WERT * PROZ / 100 else if (ARGV[i] == "%") { ARGV[i-1] = ARGV[i-1] / 100 * ARGV[i+1] ARGV[i] = "" ARGV[i+1] = "" melt = 1 } # § ~ = Prozentualer Abschlag: WERT ° PROZ -> WERT * 100 / (100 + PROZ) else if (ARGV[i] == "§") { ARGV[i-1] = 100 * ARGV[i-1] / (100 + ARGV[i+1]) ARGV[i] = "" ARGV[i+1] = "" melt = 1 } # ° = Prozentualer Unterschied: WERT § BASIS -> 100 * (WERT - BASIS) / BASIS else if (ARGV[i] == "°") { ARGV[i-1] = (ARGV[i-1] - ARGV[i+1]) / ARGV[i+1] * 100 ARGV[i] = "" ARGV[i+1] = "" melt = 1 } else continue if (DBG) DumpArgs("POT/PROZ", left, right) } if (melt) right = Melt(left, right) # Dann Multiplikation/Division (links -> rechts, Ergebnis rechts) melt = 0 for (i = left+1; i < right; ++i) { if (ARGV[i] == "*") { ARGV[i+1] *= ARGV[i-1] ARGV[i] = "" ARGV[i-1] = "" melt = 1 } else if (ARGV[i] == "/") { ARGV[i+1] = ARGV[i-1] / ARGV[i+1] ARGV[i] = "" ARGV[i-1] = "" melt = 1 } else if (ARGV[i] == "_") { ARGV[i+1] = ARGV[i-1] % ARGV[i+1] ARGV[i] = "" ARGV[i-1] = "" melt = 1 } else continue if (DBG) DumpArgs("MUL/DIV", left, right) } if (melt) right = Melt(left, right) # Zuletzt Addition/Subtraktion (links -> rechts, Ergebnis rechts) melt = 0 for (i = left+1; i < right; ++i) { if (ARGV[i] == "+") { ARGV[i+1] += ARGV[i-1] ARGV[i] = "" ARGV[i-1] = "" melt = 1 } else if (ARGV[i] == "-") { ARGV[i+1] = ARGV[i-1] - ARGV[i+1] ARGV[i] = "" ARGV[i-1] = "" melt = 1 } else continue if (DBG) DumpArgs("ADD/SUB", left, right) } if (melt) return Melt(left, right) else return right } #------------------------------------------------------------------------------- # Leere Argumente aus ARGV entfernen (bleiben nach Rechnung uebrig) #------------------------------------------------------------------------------- function Melt(left, right, pos, i) { if (DBG) DumpArgs("MELT", left, right, "NONL") # Noch gefuellte Argumente nach vorne schieben pos = left for (i = left; i <= right; ++i) { if (ARGV[i] != "") ARGV[pos++] = ARGV[i] } if (DBG) printf "-> " # Loeschen der nun ueberfluessigen (leeren) Argumente ist wichtig! for (i = pos; i <= right; ++i) { ARGV[i] = "" } if (DBG) DumpArgs("", left, pos - 1) return pos - 1 } #------------------------------------------------------------------------------- # Das am weitesten rechts stehende Klammerpaar der hoechsten Verschachtelungs- # tiefe suchen und die Positionen des Bereichs darin zurueckgeben # * Klammern vorher loeschen # * Nur eine oeffnende oder schliessende Klammer gefunden -> macht nix #------------------------------------------------------------------------------- function SubCalc(left, right, level, maxlevel, leftpar, rightpar) { if (DBG) DumpArgs("SUB", left, right, "NONL") for (i = left; i <= right; ++i) { if (ARGV[i] == "(") { ++level if (level >= maxlevel) { leftpar = i maxlevel = level } } else if (ARGV[i] == ")") { if (level == maxlevel) { rightpar = i } --level } } if (leftpar) { ARGV[leftpar] = "" ++leftpar } if (rightpar) { ARGV[rightpar] = "" --rightpar } if (DBG) { if (leftpar || rightpar) { DumpArgs(" -> ", leftpar, rightpar, "NONL") } else printf " -> none" } if (DBG) print "" return (leftpar " " rightpar) } #------------------------------------------------------------------------------- # Formel-Teil mit Einleitungstext und Positionsbereich ausgeben #------------------------------------------------------------------------------- function DumpArgs(prefix, left, right, postfix, i) { printf prefix "(" left "," right if (postfix != "NONL" && postfix != "") printf postfix else printf "):" for (i = left; i <= right; ++i) { if (ARGV[i] != "") printf(" " ARGV[i]) else printf " _" } if (postfix == "NONL") ; else if (postfix == "") print "" else printf postfix } #------------------------------------------------------------------------------- # ])} BRACE MATCHING HACK #-------------------------------------------------------------------------------