Ajout de très grands nombres dans le script shell

8

Supposons que deux nombres soient stockés dans deux fichiers différents, a.txtet b.txt.

Chaque numéro est suffisamment grand (plus de 30 chiffres) pour ne pas être pris en charge par le type de données numérique utilisé par bash.

Comment puis-je les ajouter dans le shell?

voldemort619
la source
Personnellement, j'utiliserais pythonou similaire dans ce cas.
phk
Vous êtes sûr de ne pas utiliser plutôt sed pour l'ajout ?
Jeff Schaller
Il y a quelque temps, dans ma classe java, nous avons utilisé des piles pour ajouter des nombres qui étaient hors de la plage max int de java. En supposant que vous êtes prêt à vous donner la peine d'implémenter la pile à l'aide de tableaux dans bash, vous pouvez le faire. . . mais c'est très redondant. . . et inutile comme vous pouvez le voir dans les réponses ci-dessous. Ou utilisez simplement pythoncomme l'a suggéré phk
Sergiy Kolodyazhnyy

Réponses:

12

En supposant qu'il s'agit de nombres décimaux, vous pouvez faire:

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

Attention, les sauts de bcligne sont très longs (plus de 68 ou 69 chiffres selon l'implémentation). Avec GNU bc, vous pouvez le désactiver en définissant la BC_LINE_LENGTHvariable d'environnement sur 0, comme avec:

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

L'astuce est de ne pas utiliser bashpour effectuer l'ajout 1 .

Tout d'abord, lisez chaque nombre dans une variable distincte. Cela suppose que les fichiers ne contiennent qu'un numéro et aucune autre information.

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

Utilisez ensuite la bccalculatrice pour obtenir le résultat:

bc <<<"$a + $b"

bc est un "langage arithmétique de précision arbitraire et une calculatrice".

Pour stocker le résultat dans une variable c:

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

Si la <<<syntaxe semble bizarre (elle est appelée "ici-chaîne" et est une extension de la syntaxe du shell POSIX prise en charge par bashet quelques autres shells), vous pouvez plutôt utiliser printfpour envoyer l'addition à bc:

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

Et mémoriser à cnouveau le résultat :

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

1 Utiliser bashpour effectuer l'addition de deux nombres extrêmement grands nécessiterait l'implémentation, dans le bashscript, d'une routine pour faire de l'arithmétique de précision arbitraire . C'est parfaitement faisable, mais lourd et inutile car chaque Unix est livré avec bcqui vous fournit déjà ce service de manière relativement facile et accessible.

Kusalananda
la source
1
Alternativement, vous pouvez le faire read a < a.txt. Cela permettrait également de supprimer les blancs de début et de fin, le cas échéant (en supposant qu'ils n'aient $IFSpas été modifiés).
Stéphane Chazelas
1
Pourquoi les guillemets à l'intérieur des guillemets n'ont-ils pas besoin d'être échappés pour la chaîne ici à l'intérieur de la substitution de processus?
Bryce Guinta
2
@BryceGuinta Parce que contrairement à quelque chose comme echo "\"hello\"", la chose dans le $(...)n'est pas une chaîne passée en argument à un autre programme, et le shell sait comment gérer l'imbrication des guillemets. C'est aussi pourquoi $(...)il vaut mieux utiliser plutôt que les backticks; vous pouvez écrire $( ... $( ... ) )sans aucune ambiguïté, alors que la même chose avec des backticks est ... maladroit.
Kusalananda
mais comment faire en bash sans utiliser bc
voldemort619
@ voldemort619 Vous devez implémenter additiion de la même manière que n'importe laquelle de ces bibliothèques . Vous pouvez consulter cette réponse StackOverflow pour une explication. Mais vraiment, utilisez-le bc.
Kusalananda
3

Comme Stéphane et Kusalananda l'ont dit , "vraiment, utilisez simplement bc", mais si vous voulez vraiment utiliser bash pour l'addition, voici un point de départ (entiers positifs uniquement) - je vais laisser comme exercice au lecteur de mettre en œuvre décimales et nombres négatifs:

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

J'ai laissé la bccomparaison là-dedans, mais j'ai commenté, pour comparaison.

Jeff Schaller
la source