Hinzufügen extrem großer Zahlen im Shell-Skript

8

Angenommen, zwei Zahlen werden in zwei verschiedenen Dateien gespeichert, a.txtund b.txt.

Jede Zahl ist groß genug (mehr als 30 Stellen), um vom von verwendeten numerischen Datentyp nicht unterstützt zu werden bash.

Wie kann ich sie in die Shell aufnehmen?

voldemort619
quelle
Persönlich würde ich pythonin diesem Fall verwenden oder ähnliches.
Phk
Sicher, dass Sie stattdessen nicht sed als Zusatz verwenden möchten ?
Jeff Schaller
Vor einiger Zeit haben wir in meiner Java-Klasse Stapel verwendet, um Zahlen hinzuzufügen, die außerhalb des maximalen Int-Bereichs von Java liegen. Angenommen, Sie sind bereit, sich die Mühe zu machen, Stack mithilfe von Arrays in Bash zu implementieren, können Sie dies tun. . . aber es ist sehr überflüssig. . . und unnötig, wie Sie aus den Antworten unten sehen können. Oder verwenden Sie einfach pythonwie vorgeschlagen
Sergiy Kolodyazhnyy

Antworten:

12

Angenommen, es handelt sich um Dezimalzahlen, können Sie Folgendes tun:

paste -d + a.txt b.txt | bc

Beachten Sie, dass bcZeilenumbrüche sehr lange Zahlen enthalten (je nach Implementierung mehr als 68 oder 69 Stellen). Mit GNU bckönnen Sie es deaktivieren, indem Sie die BC_LINE_LENGTHUmgebungsvariable auf 0 setzen, wie bei:

paste -d + a.txt b.txt | BC_LINE_LENGTH=0 bc
Stéphane Chazelas
quelle
10

Der Trick besteht darin , die Addition 1 nicht zu verwendenbash .

Lesen Sie zunächst jede Zahl in eine separate Variable. Dies setzt voraus, dass die Dateien nur eine Nummer und keine anderen Informationen enthalten.

a="$(<a.txt)"
b="$(<b.txt)"

Verwenden Sie dann den bcTaschenrechner, um das Ergebnis zu erhalten:

bc <<<"$a + $b"

bc ist eine "Arithmetiksprache und ein Taschenrechner mit beliebiger Genauigkeit".

So speichern Sie das Ergebnis in einer Variablen c:

c="$( bc <<<"$a + $b" )"

Wenn sich die <<<Syntax seltsam anfühlt (sie wird als "Here-String" bezeichnet und ist eine Erweiterung der von bashund einigen anderen Shells unterstützten POSIX-Shell-Syntax ), können Sie stattdessen printfden Zusatz senden an bc:

printf '%s + %s\n' "$a" "$b" | bc

Und das Ergebnis cerneut speichern :

c="$( printf '%s + %s\n' "$a" "$b" | bc )"

1 Um bashdie Addition von zwei extrem großen Zahlen durchzuführen, müsste im bashSkript eine Routine für die Arithmetik mit beliebiger Genauigkeit implementiert werden . Dies ist perfekt machbar, aber umständlich und unnötig, da jedes Unix mitgeliefert wird,bc das Ihnen diesen Service bereits auf relativ einfache und leicht zugängliche Weise bietet.

Kusalananda
quelle
1
Alternativ könnten Sie tun read a < a.txt. Dies würde auch dafür sorgen, dass führende und nachfolgende Rohlinge entfernt werden, sofern vorhanden (vorausgesetzt, $IFSsie wurden nicht geändert).
Stéphane Chazelas
1
Warum müssen die Anführungszeichen in den Anführungszeichen nicht für die Here-Zeichenfolge innerhalb der Prozessersetzung maskiert werden?
Bryce Guinta
2
@BryceGuinta Denn im Gegensatz zu so etwas echo "\"hello\""ist das Ding in der $(...)kein String, der als Argument an ein anderes Programm übergeben wird, und die Shell weiß, wie man mit der Verschachtelung von Anführungszeichen umgeht. Dies ist auch der Grund, $(...)warum es besser ist, Backticks anstelle von Backticks zu verwenden. Sie können $( ... $( ... ) )ohne Mehrdeutigkeit schreiben , während das Gleiche mit Backticks ... umständlich ist.
Kusalananda
aber wie man in bash macht, ohne bc zu verwenden
voldemort619
@ voldemort619 Sie müssten Additiion auf ähnliche Weise implementieren wie jede dieser Bibliotheken . Eine Erklärung finden Sie in dieser StackOverflow-Antwort . Aber wirklich, einfach benutzen bc.
Kusalananda
3

Wie sowohl Stéphane als auch Kusalananda sagten : "Wirklich, benutze einfach bc", aber wenn du Bash wirklich als Addition verwenden willst , ist hier ein Ausgangspunkt (nur positive ganze Zahlen) - ich überlasse es dem Leser als Übung zu implementieren Dezimalstellen und negative Zahlen:

function arbadd {
  addend1=$1
  addend2=$2
  sum=
  bcsum=$(echo $addend1 + $addend2 | BC_LINE_LENGTH=0 bc)

  # zero-pad the smallest number
  while [ ${#addend1} -lt ${#addend2} ]
  do
    addend1=0${addend1}
  done

  while [ ${#addend2} -lt ${#addend1} ]
  do
    addend2=0${addend2}
  done

  carry=0
  for((index=${#addend1}-1;index >= 0; index--))
  do
    case ${carry}${addend1:index:1}${addend2:index:1} in
      (000) carry=0; sum=0${sum};;
      (001|010|100) carry=0; sum=1${sum};;
      (002|011|020|101|110) carry=0; sum=2${sum};;
      (003|012|021|030|102|111|120) carry=0; sum=3${sum};;
      (004|013|022|031|040|103|112|121|130) carry=0; sum=4${sum};;
      (005|014|023|032|041|050|104|113|122|131|140) carry=0; sum=5${sum};;
      (006|015|024|033|042|051|060|105|114|123|132|141|150) carry=0; sum=6${sum};;
      (007|016|025|034|043|052|061|070|106|115|124|133|142|151|160) carry=0; sum=7${sum};;
      (008|017|026|035|044|053|062|071|080|107|116|125|134|143|152|161|170) carry=0; sum=8${sum};;
      (009|018|027|036|045|054|063|072|081|090|108|117|126|135|144|153|162|171|180) carry=0; sum=9${sum};;
      (019|028|037|046|055|064|073|082|091|109|118|127|136|145|154|163|172|181|190) carry=1; sum=0${sum};;
      (029|038|047|056|065|074|083|092|119|128|137|146|155|164|173|182|191) carry=1; sum=1${sum};;
      (039|048|057|066|075|084|093|129|138|147|156|165|174|183|192) carry=1; sum=2${sum};;
      (049|058|067|076|085|094|139|148|157|166|175|184|193) carry=1; sum=3${sum};;
      (059|068|077|086|095|149|158|167|176|185|194) carry=1; sum=4${sum};;
      (069|078|087|096|159|168|177|186|195) carry=1; sum=5${sum};;
      (079|088|097|169|178|187|196) carry=1; sum=6${sum};;
      (089|098|179|188|197) carry=1; sum=7${sum};;
      (099|189|198) carry=1; sum=8${sum};;
      (199) carry=1; sum=9${sum};;
    esac
  done
  if [ $carry -eq 1 ]
  then
    sum=1${sum}
  fi
  printf "Sum = %s\n" "$sum"
}

Ich habe den bcVergleich dort belassen , aber zum Vergleich auskommentiert.

Jeff Schaller
quelle