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.