Addition of extremely large numbers in shell script

6,284

Solution 1

Assuming they are decimal numbers, You could do:

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

Beware that bc line-wraps very long numbers (more than 68 or 69 digits depending on the implementation). With GNU bc, you can disable it by setting the BC_LINE_LENGTH environment variable to 0, like with:

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

Solution 2

The trick is to not use bash to perform the addition1.

First, read each number into a separate variable. This assumes that the files contain only a number and no other information.

read a <a.txt
read b <b.txt

Then use the bc calculator to get the result:

bc <<<"$a + $b"

bc is an "arbitrary-precision arithmetic language and calculator".

To store the result in a variable c:

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

If the <<< syntax feels weird (it's called a "here-string" and is an extension to the POSIX shell syntax supported by bash and a few other shells), you may instead use printf to send the addition to bc:

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

And storing the result in c again:

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

1 Using bash to perform the addition of two extremely large numbers would require the implementation, in the bash script, of a routine for doing arbitrary-precision arithmetic. This is perfectly doable, but cumbersome and unnecessary since every Unix comes with bc, which already provides this service to you in a relatively easy and accessible way.

Solution 3

As both Stéphane and Kusalananda said, "really, just use bc", but if you really want to use bash for addition, here's a starting point (positive integers only) -- I'll leave it as an exercise for the reader to implement decimals and negative numbers:

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

I've left the bc comparison in there, but commented out, for comparison.

Share:
6,284

Related videos on Youtube

voldemort619
Author by

voldemort619

Updated on September 18, 2022

Comments

  • voldemort619
    voldemort619 almost 2 years

    Suppose that two numbers are stored in two different files, a.txt and b.txt.

    Each number is large enough (more than 30 digits) to not be supported by the numeric data type used by bash.

    How may I add them in the shell?

    • phk
      phk over 7 years
      Personally I would use python or similar in that case.
    • Jeff Schaller
      Jeff Schaller over 7 years
      Sure you don't want to use sed for addition instead?
    • Sergiy Kolodyazhnyy
      Sergiy Kolodyazhnyy over 7 years
      Some time ago, in my java class we used stacks for adding numbers that were out of java's max int range. Assuming you're willing to go to the trouble of implementing stack using arrays in bash , you can do that . . . but it's very redundant . . . and unnecessary as you can see from the answers below. Or just use python as phk suggested
  • Stéphane Chazelas
    Stéphane Chazelas over 7 years
    Alternatively, you could do read a < a.txt. That would also take care of stripping leading and trailing blanks if any (assuming $IFS has not been modified).
  • Bryce Guinta
    Bryce Guinta over 7 years
    Why do the quotes inside the quotes not need to be escaped for the here-string inside the process substitution?
  • Kusalananda
    Kusalananda over 7 years
    @BryceGuinta Because unlike something like echo "\"hello\"", the thing within the $(...) is not a string passed as an argument to another program, and the shell knows how to deal with the nesting of quotes. This is also why using $(...) rather than backticks is better; you can write $( ... $( ... ) ) without any ambiguity, whereas the same thing using backticks is... awkward.
  • voldemort619
    voldemort619 over 7 years
    but how to do in bash not using bc
  • Kusalananda
    Kusalananda over 7 years
    @voldemort619 You would have to implement additiion in a similar way as any one of these libraries do. You could have a look at this StackOverflow answer for an explanation. But really, just use bc.