Adición de números extremadamente grandes en el script de shell

8

Supongamos que dos números se almacenan en dos archivos diferentes, a.txty b.txt.

Cada número es lo suficientemente grande (más de 30 dígitos) para que no sea compatible con el tipo de datos numéricos utilizado por bash.

¿Cómo puedo agregarlos en el shell?

voldemort619
fuente
Personalmente lo usaría pythono similar en ese caso.
phk
¿Seguro que no quieres usar sed para la adición en su lugar?
Jeff Schaller
Hace algún tiempo, en mi clase de Java utilizamos pilas para agregar números que estaban fuera del rango máximo int de Java. Suponiendo que está dispuesto a tomarse la molestia de implementar la pila utilizando matrices en bash, puede hacerlo. . . Pero es muy redundante. . . e innecesario como puede ver en las respuestas a continuación. O simplemente use pythoncomo sugirió phk
Sergiy Kolodyazhnyy

Respuestas:

12

Suponiendo que son números decimales, podría hacer:

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

Tenga en cuenta que la bclínea ajusta números muy largos (más de 68 o 69 dígitos dependiendo de la implementación). Con GNU bc, puede deshabilitarlo configurando la BC_LINE_LENGTHvariable de entorno en 0, como con:

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

El truco es no usar bashpara realizar la suma 1 .

Primero, lea cada número en una variable separada. Esto supone que los archivos contienen solo un número y ninguna otra información.

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

Luego usa la bccalculadora para obtener el resultado:

bc <<<"$a + $b"

bc es un "lenguaje y calculadora aritmética de precisión arbitraria".

Para almacenar el resultado en una variable c:

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

Si la <<<sintaxis se siente rara (se llama "here-string" y es una extensión de la sintaxis de shell POSIX soportada por bashy algunos otros shells), en su lugar puede usar printfpara enviar la adición a bc:

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

Y almacenando el resultado cnuevamente:

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

1 El uso bashpara realizar la suma de dos números extremadamente grandes requeriría la implementación, en el bashscript, de una rutina para hacer aritmética de precisión arbitraria . Esto es perfectamente factible, pero engorroso e innecesario, ya que viene con todos los Unix bcque ya le brindan este servicio de una manera relativamente fácil y accesible.

Kusalananda
fuente
1
Alternativamente, podrías hacer read a < a.txt. Eso también se encargaría de eliminar los espacios en blanco iniciales y finales si los hubiera (suponiendo que $IFSno se hayan modificado).
Stéphane Chazelas
1
¿Por qué no es necesario escapar de las comillas dentro de las comillas para la cadena aquí dentro de la sustitución del proceso?
Bryce Guinta
2
@BryceGuinta Porque, a diferencia de algo así echo "\"hello\"", lo que hay dentro $(...)no es una cadena que se pasa como argumento a otro programa, y ​​el shell sabe cómo lidiar con el anidamiento de comillas. Esta es también la razón por la cual $(...)es mejor usar en lugar de retroceder puedes escribir $( ... $( ... ) )sin ninguna ambigüedad, mientras que lo mismo usando backticks es ... incómodo.
Kusalananda
pero cómo hacerlo en bash sin usar bc
voldemort619
@ voldemort619 Debería implementar additiion de manera similar a como lo hace cualquiera de estas bibliotecas . Puede echar un vistazo a esta respuesta de StackOverflow para obtener una explicación. Pero realmente, solo úsalo bc.
Kusalananda
3

Como dijeron Stéphane y Kusalananda , "realmente, solo usa bc", pero si realmente quieres usar bash para agregar, aquí hay un punto de partida (solo enteros positivos): lo dejaré como un ejercicio para que el lector lo implemente decimales y números negativos:

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

Dejé la bccomparación allí, pero comenté, para comparar.

Jeff Schaller
fuente