Penambahan angka yang sangat besar dalam skrip shell

8

Misalkan dua angka disimpan dalam dua file berbeda, a.txtdan b.txt.

Setiap angka cukup besar (lebih dari 30 digit) untuk tidak didukung oleh tipe data numerik yang digunakan oleh bash.

Bagaimana saya menambahkannya di shell?

voldemort619
sumber
Secara pribadi saya akan menggunakan pythonatau serupa dalam hal itu.
phk
Tentu Anda tidak ingin menggunakan sed sebagai tambahan ?
Jeff Schaller
Beberapa waktu yang lalu, di kelas java saya, kami menggunakan tumpukan untuk menambahkan angka yang berada di luar jangkauan maks java. Dengan asumsi Anda bersedia pergi ke kesulitan menerapkan tumpukan menggunakan array di bash, Anda bisa melakukannya. . . tapi itu sangat berlebihan. . . dan tidak perlu seperti yang Anda lihat dari jawaban di bawah ini. Atau gunakan saja pythonseperti yang disarankan phk
Sergiy Kolodyazhnyy

Jawaban:

12

Anggap itu angka desimal, Anda bisa melakukan:

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

Waspadalah bahwa bcgaris-membungkus angka yang sangat panjang (lebih dari 68 atau 69 digit tergantung pada implementasinya). Dengan GNU bc, Anda dapat menonaktifkannya dengan mengatur BC_LINE_LENGTHvariabel lingkungan ke 0, seperti dengan:

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

Caranya adalah dengan tidak menggunakan bashuntuk melakukan penambahan 1 .

Pertama, baca setiap angka menjadi variabel yang terpisah. Ini mengasumsikan bahwa file hanya berisi angka dan tidak ada informasi lain.

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

Kemudian gunakan bckalkulator untuk mendapatkan hasilnya:

bc <<<"$a + $b"

bc adalah "bahasa aritmatika dan kalkulator presisi arbitrer".

Untuk menyimpan hasilnya dalam variabel c:

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

Jika <<<sintaksnya terasa aneh (ini disebut "di sini-string" dan merupakan ekstensi ke sintaks shell POSIX yang didukung oleh bashdan beberapa shell lain), Anda dapat menggunakan printfuntuk mengirim tambahan ke bc:

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

Dan menyimpan hasilnya clagi:

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

1 Menggunakan bashuntuk melakukan penambahan dua angka yang sangat besar akan membutuhkan implementasi, dalam bashskrip, dari rutinitas untuk melakukan aritmatika presisi arbitrer . Ini sangat bisa dilakukan, tetapi rumit dan tidak perlu karena setiap Unix datang bcyang sudah menyediakan layanan ini kepada Anda dengan cara yang relatif mudah dan dapat diakses.

Kusalananda
sumber
1
Atau, Anda bisa melakukannya read a < a.txt. Itu juga akan menangani pengosongan awal dan akhir kosong jika ada (dengan asumsi $IFSbelum dimodifikasi).
Stéphane Chazelas
1
Mengapa tanda kutip di dalam tanda kutip tidak perlu diloloskan ke sini-string di dalam proses substitusi?
Bryce Guinta
2
@BryceGuinta Karena tidak seperti sesuatu seperti echo "\"hello\"", hal yang ada di dalamnya $(...)bukan string yang diteruskan sebagai argumen ke program lain, dan shell tahu bagaimana menghadapi penawaran yang bersarang. Ini juga mengapa menggunakan $(...)daripada backticks lebih baik; Anda dapat menulis $( ... $( ... ) )tanpa ambiguitas apa pun, sedangkan hal yang sama menggunakan backticks adalah ... canggung.
Kusalananda
tetapi bagaimana melakukannya di bash tidak menggunakan bc
voldemort619
@ voldemort619 Anda harus mengimplementasikan additiion dengan cara yang sama seperti yang dilakukan oleh perpustakaan ini . Anda bisa melihat jawaban StackOverflow ini untuk penjelasan. Tapi sungguh, gunakan saja bc.
Kusalananda
3

Seperti kata Stéphane dan Kusalananda , "sungguh, gunakan saja bc", tetapi jika Anda benar-benar ingin menggunakan bash sebagai tambahan, inilah titik awal (hanya bilangan bulat positif) - Saya akan meninggalkannya sebagai latihan bagi pembaca untuk menerapkan angka desimal dan negatif:

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

Saya telah meninggalkan bcperbandingan di sana, tetapi berkomentar, untuk perbandingan.

Jeff Schaller
sumber