Добавление чрезвычайно больших чисел в сценарии оболочки

8

Предположим, что два числа хранятся в двух разных файлах, a.txtи b.txt.

Каждое число достаточно большое (более 30 цифр), чтобы его не поддерживал числовой тип данных, используемый bash.

Как я могу добавить их в оболочку?

voldemort619
источник
Лично я бы использовал pythonили подобное в этом случае.
PhK
Уверен, что вы не хотите использовать sed для дополнения ?
Джефф Шаллер
Некоторое время назад в моем классе java мы использовали стеки для добавления чисел, выходящих за пределы максимального диапазона java. Предполагая, что вы готовы приступить к реализации стека с использованием массивов в bash, вы можете сделать это. , , но это очень избыточно. , , и не нужно, как вы можете видеть из ответов ниже. Или просто используйте pythonкак подсказал phk
Сергей Колодяжный

Ответы:

12

Предполагая, что они десятичные числа, вы можете сделать:

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

Остерегайтесь того, что bcстроки переносят очень длинные числа (более 68 или 69 цифр в зависимости от реализации). С помощью GNU bcвы можете отключить его, установив BC_LINE_LENGTHпеременную окружения в 0, например:

paste -d + a.txt b.txt | BC_LINE_LENGTH=0 bc
Стефан Шазелас
источник
10

Хитрость заключается в том, чтобы не использовать bashдля выполнения сложения 1 .

Сначала прочитайте каждое число в отдельную переменную. Это предполагает, что файлы содержат только номер и никакой другой информации.

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

Затем используйте bcкалькулятор, чтобы получить результат:

bc <<<"$a + $b"

bc является "арифметическим языком произвольной точности и калькулятором".

Чтобы сохранить результат в переменной c:

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

Если <<<синтаксис кажется странным (он называется «здесь-строкой» и является расширением синтаксиса оболочки POSIX, поддерживаемого bashнекоторыми другими оболочками), вместо этого вы можете использовать printfдля отправки дополнения bc:

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

И сохраняем результат cснова:

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

1 Использование bashдля сложения двух чрезвычайно больших чисел потребует реализации в bashскрипте подпрограммы для выполнения арифметики произвольной точности . Это вполне выполнимо, но обременительно и не нужно, поскольку каждый Unix поставляется с таким, bcкоторый уже предоставляет вам эту услугу относительно простым и доступным способом.

Кусалананда
источник
1
В качестве альтернативы, вы могли бы сделать read a < a.txt. Это также позаботится об удалении начальных и конечных пробелов, если таковые имеются (при условии, $IFSчто они не были изменены).
Стефан
1
Почему кавычки внутри кавычек не нужно экранировать для строки здесь внутри процесса подстановки?
Брайс Гуинта
2
@BryceGuinta Потому что в отличие от чего-то подобного echo "\"hello\"", вещь внутри $(...)не является строкой, передаваемой в качестве аргумента другой программе, и оболочка знает, как обращаться с вложением кавычек. Это также, почему использование, $(...)а не backticks лучше; Вы можете писать $( ... $( ... ) )без какой-либо двусмысленности, тогда как то же самое с использованием обратных кавычек ... неудобно.
Кусалананда
но как это сделать в bash, не используя bc
voldemort619
@ voldemort619 Вы должны реализовать добавление аналогично любой из этих библиотек . Вы могли бы взглянуть на этот ответ StackOverflow для объяснения. Но на самом деле, просто используйте bc.
Кусалананда
3

Как сказал Стефан и Кусалананда , «на самом деле, просто используйте bc», но если вы действительно хотите использовать bash для добавления, вот отправная точка (только положительные целые числа) - я оставлю это в качестве упражнения для читателя, чтобы реализовать десятичные и отрицательные числа:

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

Я оставил bcсравнение там, но закомментировал, для сравнения.

Джефф Шаллер
источник