Aggiunta di numeri estremamente grandi nello script di shell


8

Supponiamo che due numeri siano memorizzati in due file diversi a.txte b.txt.

Ogni numero è abbastanza grande (più di 30 cifre) per non essere supportato dal tipo di dati numerico utilizzato da bash.

Come posso aggiungerli nella shell?


Personalmente userei pythono simili in quel caso.
phk,

Sicuro di non voler usare sed invece per l'aggiunta ?
Jeff Schaller

Qualche tempo fa, nella mia classe java abbiamo usato stack per aggiungere numeri che erano al di fuori dell'intervallo massimo int di java. Supponendo che tu sia disposto a risolvere il problema dell'implementazione dello stack usando array in bash, puoi farlo. . . ma è molto ridondante. . . e inutili come puoi vedere dalle risposte qui sotto. O semplicemente usa pythoncome suggerito phk
Sergiy Kolodyazhnyy,

Risposte:


12

Supponendo che siano numeri decimali, puoi fare:

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

Fai attenzione che il numero di bcrighe molto lunghe avvolge una riga (più di 68 o 69 cifre a seconda dell'implementazione). Con GNU bc, puoi disabilitarlo impostando la BC_LINE_LENGTHvariabile di ambiente su 0, come con:

paste -d + a.txt b.txt | BC_LINE_LENGTH=0 bc

10

Il trucco è non usare bashper eseguire l'aggiunta 1 .

Innanzitutto, leggi ogni numero in una variabile separata. Ciò presuppone che i file contengano solo un numero e nessuna altra informazione.

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

Quindi utilizzare la bccalcolatrice per ottenere il risultato:

bc <<<"$a + $b"

bc è un "linguaggio e calcolatrice aritmetica di precisione arbitraria".

Per memorizzare il risultato in una variabile c:

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

Se la <<<sintassi sembra strana (si chiama "qui-stringa" ed è un'estensione della sintassi della shell POSIX supportata da bashe alcune altre shell), è possibile invece utilizzare printfper inviare l'aggiunta a bc:

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

E memorizzare cnuovamente il risultato :

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

1 L' utilizzo bashper eseguire l'aggiunta di due numeri estremamente grandi richiederebbe l'implementazione, nello bashscript, di una routine per l'esecuzione dell'aritmetica di precisione arbitraria . Ciò è perfettamente fattibile, ma ingombrante e non necessario poiché ogni Unix viene fornito con bcquesto servizio già fornito in modo relativamente semplice e accessibile.


1
In alternativa, potresti farlo read a < a.txt. Ciò si occuperebbe anche di rimuovere eventuali spazi vuoti iniziali e finali (supponendo che $IFSnon sia stato modificato).
Stéphane Chazelas,

1
Perché le virgolette all'interno delle virgolette non devono essere salvate per la stringa qui all'interno della sostituzione del processo?
Bryce Guinta,

2
@BryceGuinta Perché a differenza di qualcosa del genere echo "\"hello\"", la cosa all'interno di $(...)non è una stringa passata come argomento a un altro programma e la shell sa come gestire l'annidamento delle virgolette. Questo è anche il motivo per cui $(...)è meglio usare piuttosto che i backtick; puoi scrivere $( ... $( ... ) )senza alcuna ambiguità, mentre la stessa cosa usando i backtick è ... imbarazzante.
Kusalananda

ma come fare in bash non usando bc
voldemort619,

@ voldemort619 Dovresti implementare la dipendenza in modo simile a una qualsiasi di queste librerie . Puoi dare un'occhiata a questa risposta StackOverflow per una spiegazione. Ma davvero, basta usare bc.
Kusalananda

3

Come hanno detto sia Stéphane che Kusalananda , "davvero, usa solo bc", ma se vuoi davvero usare bash per l'addizione, ecco un punto di partenza (solo numeri interi positivi) - lo lascerò come un esercizio da implementare per il lettore decimali e numeri negativi:

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"
}

Ho lasciato il bcconfronto lì dentro, ma commentato, per confronto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.