8

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?

  • Personally I would use python or similar in that case. – phk Jan 27 '17 at 13:03
  • Sure you don't want to use sed for addition instead? – Jeff Schaller Jan 27 '17 at 15:40
  • 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 – Sergiy Kolodyazhnyy Jan 28 '17 at 18:37
12

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
10

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.

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

Then use the bc calculator to get the result:

bc <<<"$a + $b"

bc is a "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.

  • 1
    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). – Stéphane Chazelas Jan 27 '17 at 15:16
  • 1
    Why do the quotes inside the quotes not need to be escaped for the here-string inside the process substitution? – Bryce Guinta Jan 27 '17 at 21:57
  • 2
    @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. – Kusalananda Jan 27 '17 at 22:01
  • but how to do in bash not using bc – voldemort619 Jan 28 '17 at 7:57
  • @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. – Kusalananda Jan 28 '17 at 8:11
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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.