設計は名前のことだけ考えろ

ドゥルーズという学者は

哲学とは概念を創造することだ

と述べていますが、現代で「概念の創造」という仕事を最も行っている職種は哲学者ではなくてプログラマでしょう。辞書を引くとコンピュータ用語が多く出てきます。それだけ新しい概念が作られたということです。辞書に載っている単語はもはや専門用語ではなくて、説明なしに一般に使うことが許される言葉ですが、辞書に載っていない専門家の間で常用される用語はもっと多いです。計算機が発明されて以来、膨大な概念が発見・発明されてきました。新しい用語を作るのが仕事だと思われている業界なので、現在も日々新語が生まれています。

言語というのは強力な思考ツールで、概念を抽出し、名前をつけると、さも実体があるかのように認識できるようになります。計算機自体は物理的な実体がありますが、その上で動いているプログラムは物理的な実体がありません。人間がこれを認識するには、概念を整理して適当な名前をつけて言語化しなければなりません。システムを導入する業務に対しても同じことが言えるでしょう。業務も物理的な実体がないので、概念を創造しなければ認識できません。

概念の創造は「モデリング」と呼ばれる仕事です。これができないと、おかしなシステムやプログラムになってしまいます。分かってない人がわけわからずに作るのだから当然です。

モデリングを別の言い方で呼ぶと「名付け」です。正しいモデルには適当な名前を与えることができます。適当な名前は、概念を明らかに表し、表すものが広すぎも狭すぎもせず、冗長すぎずシンプルです。おかしな名前がついているときは、モデルがおかしいとみなすべきです。ネーミングセンスがないために気の利いた名前が付いていないことはありえますが、モデルを上手く表せない名前しか付けれない場合はモデルの方が間違っています。名前のないものは認識できないので、名前から概念を作っていかないと上手くいきません。

モデリング/名付けは、抽象的な思考でストレスがかかる作業であり、凝ろうと思えばとことん凝れるが手を抜くことも簡単にできてしまうので、易き流れて先に進めてしまいがちです。しかし、そこを踏ん張って十分に煮詰めた方が良いと思います。名前重要という格言がありますね。

ただ、モデリング/名付けは語彙力が必要であり、ものを知らない人がやっても、ないものは出てこない。できないものはどうしようもないので、現実的にできる範囲でやればよいでしょう。 type や class を kbn (区分) としてしまうのも仕方のないことだと思います。さすがに kbn は嫌だと思う良心のある人は、語彙力を鍛えていくべきでしょう。

まずは日本語の語彙。そもそも業務(ドメイン)の言葉が変だったりします。さきほどの区分を表す日本語はそれ以外にも、タイプ、クラス、分類、類別、類、型、種、級、いくつもでてきます。日頃から言葉に注意しなければ、語彙は増えない。あと辞書を引くこと。サラリーマンに学を求めるべきではないのかもしれませんが、サラリーマンに理解できないことの一つが誰も辞書を持ってないことです。

次に英単語。私が気に入っているのは Lingvist という楽天が提供している英単語学習サービスと、thesaurus つまり類語辞典です。thesaurus に関しても、どうして誰も持っていないのか理解できないです。type の類語として、class, kind, group, category, division があることを知っていて、それらがポンポン出てくる人なんていないはずです。

最後にプログラム特有の語彙。これは辞書に載っていないが、慣例化しているものがかなりあります。例えば、 push/pop。データが並んでいて、末尾にデータを追加する/末尾からデータを取り出すという意味だが、知らなければ読めないし書けない。他には、Builder という名前のクラスがあったとしても、デザインパターンを知らなければ、何をするためのクラスか分からないだろう。これらは辞書には載っていないので、知らなくても仕方はないと思うが、知らないからと怒り出さないようにはしたい。プログラマは世界に 1000 万人もいるので、プログラミングの用語の辞書には需要があるでしょう。

全ての言葉を覚えるなんて不可能ですが、名前が重要と気づいて、自分の語彙の幅が狭いと知ったならば、ちょっとは勉強した方が便利だと思います。

パッケージングと付加価値

安く買って高く売るは、商売の基本です。世の中には仲介業者というのが大勢いて、(ちゃんと調べたことはないですが)労働者のかなりの割合が仲介業をしていると思われます。

しかし、今や21世紀であって、これだけ IT 化が進んできますと、ただ右から左にものを流すだけの商売というのは難しくなってきているでしょう。インターネットが全世界をほぼリアルタイムで、ほとんど無料で接続してます。仲介業者によって従来行われていた業務は、ネットワークの上だけで、以前よりも上手く達成されるようになりました。インターネット黎明期に創業して今や大企業になっている会社は、そういう事業をしている会社が多いです。

彼らは世の中をますます効率化してどんどん事業を伸ばしていくべきだと思いますが、仲介業者が彼らに駆逐されないためには、ただ橋渡しをするだけでなくて、もっと付加価値を生めるようになるべきでしょう。単に効率的になるだけでは、社会は発展しないし、つまらないですから。

付加価値を生むとは、つまり、ひと手間を加えるということです。今も言われているのかは知らないですが、日本国は加工貿易といって、原材料を輸入し、加工して別の形に変えたものを輸出していました。工業は大規模すぎて今さら参入できないですが、ちょっとひと手間加えて見栄えを変えるだけで、付加価値というのは生まれます。付加価値のない商売は、とことん値切られた上で、誰も買わなくなると思いますが、ひと手間が入っている商売は生き残るし、今後求められるのはそのひと手間だと思います。

ひと手間の加えようは無数にあるわけですが、現在最も有望だと私が考えているのは、パッケージングです。これはどういうことかといいますと、商品を組み合わせたり、組み替えたりして、使いやすい形態にすることです。

例えば、

  • 漫画全巻ドットコム。マンガの単行本は、なぜかバラ売りされていますが、全て揃って一つの作品なので、まとめて売るというだけで消費者が使いやすい形態になります。

  • リサイクルショップ。どこで買ったか忘れてしまいましたが、引っ越したときに洗濯機と冷蔵庫と電子レンジのセットを買いました。急いでいたので、悩まずに買うことができて、大変便利でした。

  • FOLIO。これは新興の証券会社でして、テーマに対して投資ができるという新しい形態の金融商品を提供しています。株は 100 株か 1000 株の単位でしか基本は売買できないので、多くの会社には投資できないものです。しかも、会社というものはやたらと数があるけれども、普通の人は個々の会社なんて興味がないので、選べません。FOLIO は、株を1株ずつにバラした上で、テーマという形で複数の会社をまとめることで、消費者にとって魅力的な形態に株を組み替えてしまいました。私はここの商品には惚れ込んでいまして、この会社がどう化けるのか興味津津です。

これらはただ既存のものを組み合わせるだけなので、誰でも思いつきそうなのですが、思いつかないものです。そして、簡単に真似できそうだけれども意外と真似ができないものです。真似しようとしたらビジネスモデルを作り替えなければならないので、新規にはじめるよりコストがかかってしまいます。また、パッケージングの過程は外から見えないので、そこは真似できないからです。

ものなんて溢れかえっていて、供給過剰な昨今です。ネットで調べれば、大抵のものが安く手に入ります。しかし、消費者には多すぎて、探せないし選べない。単品の商品で差別化するのは今や無理です。違いなんてほとんどなくて、実用上はどれでも同じなんだから。また、消費者は、手に入るものを全てを管理し消費しきることが、もうできなくなってしまっています。なので、売り手としては、パッケージングを上手くして、消費者が買いやすくすることが、今後の仲介業のあるべき姿ではないでしょうか。

この世に要件定義ができる人間は何人いるのだろう?

ITエンジニアの求人倍率が5倍以上と、非常に高い。世間ではエンジニアにやらせたい仕事の数に比べて、エンジニアの数が極端に不足しているということです。しかし、このことは私の感覚に合っていない。エンジニアの層は薄くて総数は少ないように思いますが、それでも余っているような気がしてます。

何もしていないわけではないが、別にやらなくてもいいことをやっている人が多いように思うのです。仕事をする人の数に対して、やるべき仕事が少なすぎるように感じています。やらなくていい開発をしなければ、エンジニア不足なんて問題は存在しないのではないでしょうか。

以前にいた会社では、企画を作れる人なんていなかったので、企画なしになんとなく開発が行われていた。多分あれは、開発をする人の仕事を維持するためだけの仕事だったのでしょう、あるいは管理職の仕事を維持するためのものだったのでしょう。人は余っていて、アイドリングのような仕事をさせるにしても、仕事を回すには人が足りない。エンジニア不足といっても実際のところは、このような事情なのではないかと推測します。

無意味な仕事をするなんて不幸なことだと思うのですが、それが平気な人の方が多いようです。もし意味を見出しているなら、単に見解の相違なので、無意味とか言ってごめんなさいと謝ります。そうでなくて、これは本当にやるべきことなのかとか考えないか、考えてないようにしている人が大多数だと思います。目の前のタスクをこなしていたら意義なんて分からないものですし、そんなこと考えない方で作業した方が、効率がよいからです。こういう人々を仮に作業者と呼びましょう。作業者はおそらく不足していない。やらなくていい仕事が多すぎるだけです。

しかし、やるべき仕事をつくることができる人は一体どれほどいるのだろう。事業を起こす人は、ほどんどいないし、そこまで大勢は要らないでしょう。しかし、要求・要件を定義したりすることができる人というのは、作業者の数に対して少なすぎると思います。要求者が言うことをそのまま転記する仕事のことを言っているのでない。いわゆる真の要求を見つけ出したり、論点を明らかにできる人のことを言っている。ぼんやりとしか言及できないのは私がそれをできないからです。

仕事の価値なんて何をするかでほとんど決まります。やろうとしていることが正しかったら、実装の品質なんて少々劣っていてもよいとさえ思います。前述の以前いた会社の人は、うちの製品は商品性はないが品質は高いと言っていましたが、それを品質が低いというのではないでしょうか。実装の品質も本当にこれ売っているのかと疑問に思うようなものでしたが。要件定義が正しければ、品質なんて後からついてくると思います。

私は、何かを間違ってエンジニアとかやっているが、基本的に開発が嫌いなので、作って動いたなんて仕事だと思っていない。これをする意味あるのかとしばしばぶーたれている。このスタンスが果たして好ましいのかは別として、この視点から見たら、世の中には問題は山積みだけれども、それを要件にできる人が少なすぎるがために、やるべきでない仕事が多すぎるように思うわけです。

ぼんやりとしたイメージしかまだ持てないのではあるが、大して難しいことのようにも思わない。私はできないのだが、どうしてみんなもできないしやらないのだろうと不思議に思う。そもそもこれは誰がするべき仕事なのだろうか。コンサルタントの仕事なのだろうか。供給の少なさに比して高い需要があると見ているので、みんなやったらいいのにと思うし、そうしたらみんなが幸せになるのではないだろうか。

難しいことは仕事ではない

仕事が難しくなければならないなんて一体誰が決めたのでしょう。このことを誤解している人が多いように思いますし、そのために私もずいぶん長らく勘違いをしていました。しかしながら、考えを改めまして最近では、難しいことは仕事ではないと思っております。

難しいことに価値があると考えることが、そもそも間違いなのだと思います。仕事の難しさ仕事の価値は必ずしも相関しません。簡単だけれども有意義な仕事は少なくないですし、難しそうであっても無意味な仕事はもっと多いです。

しかし、難しいから偉いとなぜか人は思ってしまいます。この考えは危険です。なぜならば、難しいことに価値を見出してしまうと、どうでもいい仕事でさえいたずらに難しくしてしまうからです。チャレンジングなどの言葉も下手に使うと罠に陥りやすい危険な単語でしょう。

生産性という単語を最近よく耳にしますが、仕事に難しさだとかチャレンジングさを求めると、著しく生産性が下がるように思います。生産性とは安定して生産することです。困難なことに挑むならば安定した結果は期待してはいけないでしょう。加えて簡単なことも難しく難しくしようものなら、生産性なんて諦めるべきです。意味なしに難しい仕事なんて非生産的な仕事なのだから、そこに生産性を求める方が間違っています。簡単な仕事を安定してこなしているのが最も生産性が高い状態でしょう。

難しいことはやらないというのが、安全な態度だと思うのです。難易度をフラットな視点で見据えて、易しいなら易しいなりに、難しいなら難しいなりにこなせるならいいと思います。しかし、それは無理で、チャレンジングなことをやろうなどとすると、難しいことは余計に難しくなり、引っ張られて簡単なことも難しくなってしまうものです。

特に指導する側の人は、仕事を難しくしたがる病から脱しなければならないと思います。上の人が仕事は難しくなければならないと思っていたら、仕事を難しくしなければならないと勘違いしてしまうでしょう。そうしたら余計な重しに足を取られて、全員が不幸になると思うのです。

もし偉い人が呪縛から抜けられず自分だけ抜けたなら、簡単な仕事をあたかも難しいようにこなすことは処世術として有効でしょう。仕事自体の価値が評価できない人は、どれだけ大変そうに仕事をしたかでしか評価できないのですから、仕方ありません。

あるいは、みんなが難しいと思っていることが、自分には簡単そうに見えたらそれは是非ともやったらいいと思います。偉業を行った人はしばしば、それが難しいことだとは知らなかった、と言います。

難しいことなんて取り組んだところで疲弊するだけで、何もいいことはないから、そこから積極的に逃げるのが、プロフェッショナルとして正しい態度なように思います。

オブジェクトは目的語

オブジェクト指向プログラミング言語ではメソッドを呼び出したいとき、

<インスタンス名>.<メソッド名>

インスタンスを示す変数の名前のあとに、そのオブジェクトに属するメソッドの名前を示します。このプログラミング言語の構文は、誤解も招く良くない構文であったと思います。 <インスタンス名>.<メソッド名> の構文は英語の SV 文型を連想させるので、インスタンスが主語でメソッドが動詞であると勘違いしてしまいます。

しかし、実際にはオブジェクトを目的語とした方が自然な――人間に優しい――プログラムとなるように思います。 Java の標準ライブラリを見ても、オブジェクトが目的語となっているものが多いです。例えば Callable インターフェイスは call() メソッドを持ちます。 "callableObject.call();" とプログラム上は書きますが、英語で書くと "call the callable-object." を意味していると考えるのが自然です。他にも、 String 型の split メソッドも説明に "Splits this string around matches of the given regular expression." と書かれています。プログラミング言語では文は目的語の次に動詞が来るような形になります。主語が明らかではないかと思われるのですが、これは自明でメソッドの呼び出し元です (プログラムを書く人でも計算機でもいいですが)。

オブジェクトが主語になってしまうプログラムはあまり良いプログラムとはいえません。例えば、FooManager のような名前のクラスを作って、 "manager.doSomething();" と書くこともできるのですが、別にこれなら static メソッドで記述すれば十分なわけでクラスにする意味がありません。そもそもクラスとはあくまでデータ構造であって、そのデータに対する操作がメソッドです。操作の対象とはつまり目的語のことです。

プログラミング言語は決して英語ではないのですが、英語らしいプログラムが正しいプログラムだとしばしば思われているように思います。 というより私がそのような勘違いをしていました。 <インスタンス名>.<メソッド名> の構文は、そのような考えを持っていたときに、適切でない設計を招いてしまいます。これはプログラミング言語の欠陥だと思います。

例外は真偽値を返すメソッドです。Java の String 型だと、 contains() や isEmpty() が挙げられます。 "string.contains(sequence)" だとか "string.isEmpty()" という形で使われ、これらの文はオブジェクトが主語となった命題として振る舞います。オブジェクトが目的語となる場合の文は命令であり、オブジェクトが主語となる場合の文は命題です。既存のプログラム言語は、命令と命題が区別がなく、この点も良くないと思っています。

参考

ソフトウェアの設計など不要では?

ソフトウェアの設計技法は数多く提唱されてきました。構造化設計から始まり、オブジェクト指向設計デザインパターンドメイン駆動設計、など。私も趣味でこれらについてかなり勉強してきたように思います。問題を綺麗に解くことが好きだからでしょう。

しかし、単に私が不幸なだけかもしれないのですが、現実世界では人類が発明してきた設計技法は無視されているように思えます。設計工程と呼ばれるものはありますが、単にそれは書類をつくる仕事であって、設計などされていないようです。そもそも、設計ができる人はほとんどいないし、設計の良し悪しを評価できる人も限られています。こう書くと、私はできるのに他の人の程度が低いように聞こえますが、私もちゃんとした設計はできない; というか思想もなく一貫もしていない雑なものしか触れず、設計ができるかどうかを確かめる機会に恵まれなかった。

現実は美しくもない雑な仕事ばかりなのではあるが、それらに価値がないかと全くそんなことはない。美しさなんてのは、仕事の価値とは全く関わりのないものです。社会という巨大なシステムに組み込まれて、日々動いていることが価値でしょう。なんとなく作りたいから作って、使われてもいないようなものは、無価値でしょう。

ただ動くものを作るだけであれば、はたして設計なんて必要だろうか。別になくてもいいじゃないかと思っている。だって世の中それで動いているじゃないですか。モジュールに分割されていなくたって、システムとしては動く。逆に、モジュール単体で動くようにつくるのは結構難しい。Java であれば頑張ればできるが、 C や COBOL だったらほぼ無理だ。単体で動かないようなものにシステムを分割する意味ってあるのだろうか? どうせ結合テストシステムテストしかやらないのだったら、それに合格するだけの仕事で十分だろう。そして、昔やっていなかったのに、Java だとかの登場でモジュール単体の試験ができるようになったからといって、細かく設計をしなければならない理由はない。「作れたからなんとなく作っちゃいました」という人は捻り殺したくなるが、それと同様にできるようになったからやりましたもありえない思う。

設計にはコストつまり時間がかかる。設計なんかせずに、フラットな一つのルーチンにコピペしながら、ダラダラと書いていくのが最も生産性は高い。動くだけだったらそれで十分だ。また、設計なんてしたら性能が下がる。モジュールに分割したら、モジュール間で通信のコストが必要になる。完全にモノリシックなシステムの方が性能は高い。設計をしなければ、安くて速いものが手に入るのだ。

できる人もいなくて、やる意味も大してないなら、もうやらなくていいんじゃないかな?

有効数字で浮動小数点数を丸める

浮動小数点数の時系列データを圧縮するのに xor encoding という手法があるそうだ。

値が時系列に 15.5, 14.0625, 3.25, 8.625 というように並んでいたとする。

それぞれを倍精度浮動小数点で表すと、ビット列は下のようになる。

decimail double precision
15.5 01000000 00101111 00000000 00000000 00000000 00000000 00000000 00000000
14.0625 01000000 00101100 00100000 00000000 00000000 00000000 00000000 00000000
3.25 01000000 00001010 00000000 00000000 00000000 00000000 00000000 00000000
8.625 01000000 00100001 01000000 00000000 00000000 00000000 00000000 00000000

時系列データというのは、同じ値が続いたり、前後の値が近いという特徴がある。そのとき、近い数字はビット列の並びも近いとすると、前の値ビット列と XOR を取ると、ビットが立っているところが集中するようになる。

00000000 00000011 00100000 00000000 00000000 00000000 00000000 00000000
00000000 00100110 00100000 00000000 00000000 00000000 00000000 00000000
00000000 00101011 01000000 00000000 00000000 00000000 00000000 00000000

ビットが立っているところのデータだけ記録すれば、保存に必要なディスク容量が少なくなるだろうというアイデアです。ビットが立っているところだけ記録する方法は Gorilla の論文を参照してください。

しかし、この手法は本当にいいのだろうか?

浮動小数点数のバイナリデータをダラダラと吐き出すシステムもないわけではないのだろうが、ほとんどの場合10進数のテキストデータが渡されるのではなかろうか。10進数を浮動小数点数に厳密に変換することはできません。例えば、 3.14 を倍精度浮動小数点にしたときのビット列は

01000000 00001001 00011110 10111000 01010001 11101011 10000101 00011111

となります。これを10進数に戻すと 3.14000000000000012434… となります。

3.13 を倍精度浮動小数点にしたら、

01000000 00001001 00001010 00111101 01110000 10100011 11010111 00001010

となるのですが、3.14 と XOR をとったら

00000000 00000000 00010100 10000101 00100001 01000000 01010010 00010101

です。2進数でキリが悪い数字となっているので、値は近いにも関わらず、ビット列はそこまで一致していません。

そもそも、3.14, 3.13 は有効数字は3桁であり、倍精度浮動小数点は10進数で約16桁の精度なので、10進数をそのまま浮動小数点数に直すと無効な桁も表現してしまっていることになります。有効数字だけ残して、無効な桁を表現するビットを丸めてしまえば、もっと効果的にデータを圧縮できそうです。

3.14 の場合は、下のように有効数字を示す右側のビットを落としてしまってよいでしょう。

丸めなし 01000000 00001001 00011110 10111000 01010001 11101011 10000101 00011111
丸めあり 01000000 00001001 00100000 00000000 00000000 00000000 00000000 00000000

有効数字3桁で丸めた浮動小数点を10進数に戻すと、 3.140625 となります。これが2進数ではキリのよい数ということです。記事の最後にこのような丸めを実現するプログラム例を載せておきます。

テキストの 10進数 → 有効数字で丸めた浮動小数点 → xor encoding

という風に変換していけば有効数字を維持したまま時系列データを圧縮できるのではないかと考えました。

実際にやってみました。Gorilla の論文に載っている符号化は実装が面倒すぎたので、代わりに前の値との xor 値を並べたバイト列を圧縮してどの程度効果があるのか試した。

sar でとった 1秒おきの PC の CPU 使用率のデータで試してみたら、以下のような結果になりました。

サイズ [bytes]
もとのテキストデータ 70062
倍精度浮動小数点数に変換後 XOR 値をとって gzip 圧縮 29357
有効数字で丸めた浮動小数点数に変換後 XOR 値をとって gzip 圧縮 26887

一応、効果はあるようです。しかし、普通にテキストファイルを gzip 圧縮したら 18431 bytes となって、最も有効だった。PC の CPU の使用率は変動が激しすぎて、そもそも xor encoding が有効でないのだろう。滑らかな変動をするようなデータで試した方が良かったのかもしれない。


import argparse
import math
import re
import struct
import sys


class DoublePrecisionFloat:
    def __init__(self):
        self.bits = None

    @staticmethod
    def from_float(f):
        instance = DoublePrecisionFloat()
        instance.bits = struct.unpack('>Q', struct.pack('>d', f))[0]
        return instance

    @staticmethod
    def from_string(string):
        f = float(string)
        return DoublePrecisionFloat.from_float(f)

    @staticmethod
    def from_bits(bits):
        instance = DoublePrecisionFloat()
        instance.bits = bits
        return instance

    @property
    def raw_float(self):
        return struct.unpack('>d', self.bits.to_bytes(8, byteorder='big'))[0]

    def to_binstr(self):
        bb = self.bits.to_bytes(8, byteorder='big')
        return ' '.join(['{:08b}'.format(b) for b in bb])

    def to_hexstr(self):
        bb = self.bits.to_bytes(8, byteorder='big')
        hex2bytes = ['{:02x}{:02x}'.format(bb[i], bb[i + 1])
                     for i in range(0, len(bb), 2)]
        return ' '.join(hex2bytes)

    @staticmethod
    def __significant_bits(significant_figures, base):
        return int(math.ceil(
            significant_figures * math.log2(base)))

    # 11111111 11110000 00000000 ... 00000000
    # |<----------><------  fraction  ------>
    # |  exponent
    # sign
    __FILTER = int.from_bytes(
        b'\xFF\xF0\x00\x00\x00\x00\x00\x00',
        byteorder='big',
        signed=True)

    def round_off(self, significant_figures, base=10):
        # 0         0.1         1
        # |<-------------------||
        #        round off
        sig_bits = self.__significant_bits(significant_figures, base)
        # left side is padded with 1
        filt = self.__FILTER >> sig_bits
        new_bits = self.bits & filt
        return DoublePrecisionFloat.from_bits(new_bits)

    def round_up(self, significant_figures, base=10):
        # 0         0.1         1
        # ||------------------->|
        #        round up

        sig_bits = self.__significant_bits(significant_figures, base)
        # left side is padded with 1
        filt = self.__FILTER >> sig_bits

        # TODO: the case of overflow
        new_bits = (self.bits + (~filt)) & filt
        return DoublePrecisionFloat.from_bits(new_bits)

    def round(self, significant_figures, base=10):
        # 0         0.1         1
        # |<--------||--------->|
        #  round off   round up

        sig_bits = self.__significant_bits(significant_figures, base)
        filt = self.__FILTER >> sig_bits
        round_up_bit = 0x0010000000000000 >> (sig_bits + 1)
        new_bits = (self.bits + round_up_bit) & filt
        return DoublePrecisionFloat.from_bits(new_bits)

    def xor(self, other):
        return DoublePrecisionFloat.from_bits(self.bits ^ other.bits)


class FloatString:
    def __init__(self, string):
        self.string = string

    @property
    def significant_figures(self):
        # TODO: This code is ugly

        string = self.string

        # 0.0
        f = float(string)
        if f == 0.0:
            return 0

        # 0.0123
        pattern1 = r'[+-]?0\.0*(\d+)'
        match1 = re.search(pattern1, string)
        if match1:
            return len(match1.group(1))

        # 123.4 or 123.40
        pattern2 = r'[+-]?0*([1-9]+\d*\.\d+)'
        match2 = re.search(pattern2, string)
        if match2:
            return len(match2.group(1)) - 1

        # +1E+10 or -1e-10
        pattern3 = r'[+-]?(\d+)[eE][+-]\d+'
        match3 = re.search(pattern3, string)
        if match3:
            return len(match3.group(1))

        # +1.23E+10 or -1.23e-10
        pattern4 = r'[+-]?0*(\d\.\d+)[eE][+-]\d+'
        match4 = re.search(pattern4, string)
        if match4:
            return len(match4.group(1)) - 1

        # 1234
        pattern5 = r'[+-]?0*(\d+)'
        match5 = re.search(pattern5, string)
        if match5:
            return len(match5.group(1))

        raise ValueError("could not get significant figures: '%s'" % string)

    def to_rounded_float(self):
        d = DoublePrecisionFloat.from_string(self.string)
        sigfig = self.significant_figures
        return d.round(sigfig)

    def to_nonrounded_float(self):
        d = DoublePrecisionFloat.from_string(self.string)
        return d


def main():
    parser = argparse.ArgumentParser()
    out_formats = parser.add_mutually_exclusive_group()
    out_formats.add_argument('-b', action='store_true',
                             help='print results with binary strings')
    out_formats.add_argument('-x', action='store_true',
                             help='print results with hex strings')
    out_formats.add_argument('-f', action='store_true',
                             help='print results with decimal number')
    out_formats.add_argument('-n', action='store_true',
                             help='not round results')
    args = parser.parse_args()

    if args.b is True:
        write_func = lambda f: print(f.to_binstr())
    elif args.x is True:
        write_func = lambda f: print(f.to_hexstr())
    elif args.f is True:
        write_func = lambda f: print(str(f.raw_float))
    else:
        write_func = lambda f: sys.stdout.buffer.write(
            struct.pack('>d', f.raw_float))

    def stdin():
        try:
            while True:
                yield input().strip()
        except EOFError:
                pass

    for line in stdin():
        if args.n is not True:
            rounded = FloatString(line).to_rounded_float()
        else:
            rounded = FloatString(line).to_nonrounded_float()
        write_func(rounded)

if __name__ == '__main__':
    main()