先日のシェル芸勉強会において、awkで巨大な数字を取り扱うという内容があった。 awkでは、巨大な数字を扱おうとすると以下のようなエラーが出力されることがあるのだけど、これを回避する方法について考えるという内容だった。

$ echo|awk '{print(999^300)}'
+inf

Stack Overflowとかを漁っていると、どうも53bit分までのデータしか扱えないため、それ以上の数字になるとinfとして扱われてしまっている模様。

c++ - Does big integer in AWK only have 53 bits? - Stack Overflow

For all of these functions, first the double precision floating-point value is converted to the widest C unsigned integer type, then the bitwise operation is performed. If the result cannot be represented exactly as a C double, leading nonzero bits are removed one by one until it can be represented exactly. The result is then converted back into a C double.

1. gawkを使う場合

よく利用されているLinuxディストリビューションやMacで通常利用されているawkはGNU AWKになると思うのだが、awkにもいくつか種類がある。 で、そのうちのgawkなら-Mオプションを付与することで大きな数字を使った処理が可能になる。(よく使われているディストリビューションならgawkは最初から入っていると思う)

echo|gawk -M '{print(999^300)}'
$ echo|gawk -M '{print(999^300)}'
740707032156099464825492750125498782923874997114130170806961001465301947538727830598812870497467597333002429369381746413743021176620344898655207751051108708579287880019440359948243215080720463734669832067616376175341390825421213050005452748768407493902025373936876451797400970604198304852801661619040441348084547289450744175249347732702399917029521145703104814593938370882400665603350003537154659459564176427697064774096972412730972085462563537635168332560169315559224328701513052621237294170155723669297217400292632571256751580897716347362490700335631129107897897453613649989596616742433021575662612278745780586699903776370240325588753171590515699672270878538542792753250573616314228667632063973166085995878058059053688620177939547636928933182592888373343798594262891750605297854013386330575193419486800497723897287405936162673840121331123962104618131416258947795731421639516864193226719944849700001

2. 外のコマンドに処理させる

処理している内容にもよるが、もしbcなどの外部に出せる処理ならコマンドを組み立てて処理するという方法もあるだろう。 普通にWEBシステムとかそういうのだとコマンドインジェクション作り込む発想だからあんまりよろしくない方法だと思うんだけど、awkを使う場面って大体コンソールでの操作だと思うのでまぁこの場合は影響ないだろう(多分)。

echo|gawk -M '{print("999^300")}'|bc
echo|gawk -M '{print("bc <<<999^300")}'|bash
$ echo|gawk -M '{print("999^300")}'|bc
74070703215609946482549275012549878292387499711413017080696100146530\
19475387278305988128704974675973330024293693817464137430211766203448\
98655207751051108708579287880019440359948243215080720463734669832067\
61637617534139082542121305000545274876840749390202537393687645179740\
09706041983048528016616190404413480845472894507441752493477327023999\
17029521145703104814593938370882400665603350003537154659459564176427\
69706477409697241273097208546256353763516833256016931555922432870151\
30526212372941701557236692972174002926325712567515808977163473624907\
00335631129107897897453613649989596616742433021575662612278745780586\
69990377637024032558875317159051569967227087853854279275325057361631\
42286676320639731660859958780580590536886201779395476369289331825928\
88373343798594262891750605297854013386330575193419486800497723897287\
40593616267384012133112396210461813141625894779573142163951686419322\
6719944849700001

$ echo|gawk -M '{print("bc <<<999^300")}'|bash
74070703215609946482549275012549878292387499711413017080696100146530\
19475387278305988128704974675973330024293693817464137430211766203448\
98655207751051108708579287880019440359948243215080720463734669832067\
61637617534139082542121305000545274876840749390202537393687645179740\
09706041983048528016616190404413480845472894507441752493477327023999\
17029521145703104814593938370882400665603350003537154659459564176427\
69706477409697241273097208546256353763516833256016931555922432870151\
30526212372941701557236692972174002926325712567515808977163473624907\
00335631129107897897453613649989596616742433021575662612278745780586\
69990377637024032558875317159051569967227087853854279275325057361631\
42286676320639731660859958780580590536886201779395476369289331825928\
88373343798594262891750605297854013386330575193419486800497723897287\
40593616267384012133112396210461813141625894779573142163951686419322\
6719944849700001

基本的にはgawkで処理するのがいいと思う。 諦めてPerlでやるという方法もあるけど、そこは状況に応じて臨機応変に対応する感じだろうか…。