Bluetooth経由でInkbirdの温度計から温度と湿度の履歴データを取得する

Bluetooth温湿度計を買って自力でIoT対応出来るようにした。
SDK配布はおろか仕様も非公開だったけど、自力でBLE接続してデータを抜くことに成功した。
Inkbird IBS-TH1 PLUSってやつ、Amazonのタイムセールで3,600円。

メーカー公式のアプリを使えば温度/湿度の履歴をCSV形式でスマホに保存出来るけど、直接Bluetooth経由で生の履歴データが欲しかった。
リアルタイムのデータしか取れないと近くにずっとスマホを置いておかなきゃいけないし、かといってメーカーのアプリ使ってCSVエクスポートするのも不便なので。

ググった感じ、アドバタイズパケットを見てリアルタイムに温度と湿度を取っている人は見かけたけど、ちゃんとGATT接続してデータ取得している人は見かけていない。
なので一応誰かのためになるかもしれないのでメモ。

アドバタイズパケット

Manufacturer Specificの先頭2byteに企業の識別子がついていない。
AndroidのBluetoothLeScannerのcallbackでscanResult.getScanRecord().getManufacturerSpecificData()すると、先頭の2byte(温度)を企業の識別子として勝手にKeyValueにされてしまうので嬉しくなかった。

Manufacturer Specificのフォーマット。

0-1: temp
2-3: hum
4: 外部温度センサーが接続されているか
5-6: ?
7: battery
8: ?

実際の値。

A308 CC10 002F 8664 08

ServiceとCharacteristic

Service: 00001800-0000-1000-8000-00805f9b34fb
Service: 00001801-0000-1000-8000-00805f9b34fb
Service: 0000180a-0000-1000-8000-00805f9b34fb
Service: 0000fff0-0000-1000-8000-00805f9b34fb
 Characteristic: 0000fff1-0000-1000-8000-00805f9b34fb ←デバイスの設定(RW)
 Characteristic: 0000fff2-0000-1000-8000-00805f9b34fb ←リアルタイムデータ(R)
 Characteristic: 0000fff3-0000-1000-8000-00805f9b34fb ←?(RW)
 Characteristic: 0000fff4-0000-1000-8000-00805f9b34fb ←?(R)
 Characteristic: 0000fff5-0000-1000-8000-00805f9b34fb ←?(R)
 Characteristic: 0000fff6-0000-1000-8000-00805f9b34fb ←通知(N)
 Characteristic: 0000fff7-0000-1000-8000-00805f9b34fb ←?(RW)
 Characteristic: 0000fff8-0000-1000-8000-00805f9b34fb ←履歴取得(RW)
 Characteristic: 0000fff9-0000-1000-8000-00805f9b34fb ←履歴削除(W)

0000fff1 デバイスの設定

更新する時はreadで取得できるものと同じフォーマットのbyte配列を書き込む。

0: ?
1-2: 内部温度センサーの補正値、摂氏の100倍のshort値
3-4: 外部温度センサーの補正値、摂氏の100倍のshort値
5-6: 内部湿度センサーの補正値、100倍のshort値
7-8: 記録する間隔、秒数
9-10: ?
11: 温度計のメジャーバージョン、ASCII code
12: ?
13: 温度計のマイナーバージョン、ASCII code
14-19: ?

実際の値。

0064 002C 01C8 003C 0000 0032 2D30 963D 0000 0000

0000fff8 履歴取得、0000fff9 履歴削除

valueがコマンドみたいな感じで1byteだけ書き込む。
書き込むとonCharacteristicChangedでなんやかんや取れる。

0000fff8
 0x01: temp content
 0x02: temp header
 0x03: hum content
 0x04: hum header
 0x07: temp content crc
 0x08: hum content crc

0000fff9
 0x01: 履歴削除

温度計の時間

温度計本体はデータを記録した時間を保持していない。

データを記録した時間は、スマホ側でスマホの日時を使ってデータのindexから相対的に計算してる。
こんな感じ。

記録した時間 = スマホのunixtime - ((温度計の内部データ.length - 1 - i) * 温度計の内部interval)

じゃあ電池抜いたらどうなるのか?とやってみたら案の定ズレた。
整合性を考えるなら、一旦電池抜いたら履歴が全部消えるのが理想だなとは思う。

問題点。
・電池を抜いたら、抜いていた時間分ズレる。(電池を抜いていた/電池が切れていたことを検知出来ない)
・平常時から最大でinterval秒ズレるので日時をキーにしづらい。

せめて前回記録したのが何秒前なのかってデータがあればまぁまぁの精度で日時を一致させられるのになぁと。

自己愛性人格障害の思想

今までの人生で明らかな自己愛性人格障害者と少なく見積もっても6人と関わってきた。
その集大成として彼らの思想を説明していく。
ここに書いた事は妙に具体的で誰か特定の一人の言動について書いたものなのでは?と思われるかもしれないが、気持ちの悪いことに複数人に共通する言動であり思想なのである。

一般的に言われている自己愛性人格障害の症状や特徴、行動パターン、原因などは他のサイトや書籍に任せる。
それらを読んだ上でこの記事を読むことをおすすめする。
そしてここに書いた自己愛性人格障害の思想を理解することで、自己愛性人格障害の特徴や行動パターンを見ても腑に落ちるようになるだろう。

自己愛性人格障害の症状や言動については以下を参照。
自己愛性人格障害の特徴 詳細版 モラハラ加害者 [ モラハラ資料 ]

もし自己愛性人格障害者との関わりがない人がこの文章を読んでも何も理解できないでしょう。
関わりがなければ理解の範疇外なのは当然だし、そういう人が身近にいないのは幸せなことである。
今後そういう人が近くに現れたとしても理解し許容する必要はないが、理解し回避することは自分の為になるので覚えておいて損はないでしょう。
既に関わりを持ってしまった被害者はご愁傷さまでした。

これ以上自己愛性人格障害のために時間と労力を割くのは人生の無駄なので構成が雑で読みづらい文章かもしれないが何卒。
この記事を以って、自ら自己愛性人格障害者に近づいて観察する人生も終わりとする。

ルールを定める人格とルールに従う人格を持つ二層人格

はたから見れば彼らは共感性がなく傲慢だが、彼らにその意識はない。
それどころか、社会のルールに従い愚直に生きているとさえ思っている。

健常者は社会のルールに沿って生きているが、自己愛性人格障害者は自分のルールに沿って生きている。
彼らは、ルールを定める人格(社会)とルールに従う人格(主観)という2段階の人格で構成されている。
彼らは都合よく自分用のルールを作り、それに忠実に従っているのだ。

「ルールを定める人格」は彼らにとっては自分の主観とは切り離されている。
彼らは自分で定めたルールを社会全体のルールであり大多数の意見だと信じていて、自分が勝手に定めた自己都合のルールだと気づいていない。
そして「ルールに従う人格」のみを自分の人格だと思い込んでいる。
だから彼らにとっては自分自身は「ルールに従う愚直な人間」であり「社会の代弁者」のような振る舞いも目立つ。

「ルールに従う人格」がただ実直にルールに沿って事をこなし続けているだけという状況を作ることで、都合の良いことは自分の功績、不都合なことはルールに従っただけで自分に非はない、という身の守り方をする。
そうやって「ルールを定める人格」と「ルールに従う人格」で役割を分担しているのだ。
彼らはルールに誠実だという認識から自身のことを、多数派、普通、正義、だと信じて止まない。

ルールを作る→乗るの2段階は健常者にはない異常な思想であり、これが自己の形成に失敗した人格障害者の根幹。
自己の形成の失敗であり、世間と自分の同一視でもある。

偉人の名言や提唱されている理論が大好き

自己肯定するために他者との比較が必須な彼らは、自身を是とするための理由を常に求め続けている。

自己愛性人格障害の、ルールを作る→乗るの2段階の具体例。
偉人の名言を自分の信念として偉人と自分を同一視したり、社会学者や心理学者の提唱する法則やら理論を世界の真理として自分がいかに正しく優れているか・他者がいかに劣っているかの理由付けにしたりするのは見ていてわかりやすい。

これらの偉人の言葉や思想、提唱されている理論や法則、法律などをひとつの側面だけを見て都合よく解釈し自身を是とできるルールを自分で作り出している。
また自身を是とするために利用しているので、元の意味合いとかけ離れて解釈していることも少なくない。

奇妙な口癖「なんで?」

他者が自分とは異なるルールで生きていると「なんで?」と聞く。

これは一般的な用途とはずれていて、目的ではなく原理を問う用途にふりきっている。
知的欲求からの問いではなく、相手が言動や思想に至る理由を求めたり、価値観に対して聞いてくる。
相手には正当性を求め自分が理解し許容できるか否かで善悪を判断する。
そして相容れない相手には、相手がおかしなことを言っている、おかしな思想に至る原因(コンプレックスや嫉妬心など)が相手にあるんだ、と相手に問題があると結論づける。
その結果、相手を非常識な人間や精神的な病気だと扱ったり人格否定をしたりする。
お互いの価値観が両立することや自分が間違っているとは考えもしないのだ。

社会でなんで?なんて問い詰めれば普通にパワハラになるということにも気づかないし「それパワハラですよ」なんて言った日には「なんで?」と返ってくること請け合い。

これを起因として自己愛性人格障害者は子育てに失敗する。
子供のなぜなぜ期を上手く接することが出来ないし、子供に対して責任を負わせる言動が多い。

奇妙な口癖「違うの?」

他者から自分のルールについて理由を求められたり指摘されると「違うの?」と不思議がる。

これもルールを作る→乗るの2段階がその人の中に存在していることを知ると理解しやすい。
彼らは自分の作ったルールを常識と捉えてそれを前提にして生きている。
だから彼らにしてみれば、意識せず前提としているルールついて指摘されてもなんでいきなりそんなことを言ってくるのか意味がわからないし、それを自分の思想だとも認識していないので説明することもできない。

健常者からすれば「なぜ青信号で渡ったんだ」と言われるくらい突拍子もないことなのだろう。
言うまでもないことだが、青信号で渡ることは常識であり正しいことである。
その当たり前に存在しているルールに沿って青信号を渡ったら支離滅裂な言い分で突っかかられて青信号で渡った理由を求められた、というような状態。
自己愛性人格障害者からすれば、自分のルールについて指摘されるというのはこの会話くらい意味のわからないことなのだ。

すべて相手に原因があると思いこむ

何か問題が起こった場合や前述の「なんで?」「違うの?」で相容れない状況はすべて相手に原因があると思いこむ。
彼らは自分の作り上げたルールに従って行動しているだけなので自分自身が問題を起こす原因になるなどあり得ないと思っている。
彼らにとって自分が正しいということは疑う余地のない事実であり前提なのである。
自分の言動を正しいと信じてやまない以上問題が起こったのも相容れないのもすべて相手に原因がある以外には考えられないのだ。
自身の正当性を主張するために相手を悪者にしているのではなく「自分は正しいのだから原因は相手にある」という辻褄合わせなのである。

奇妙な口癖「わからない」

彼らが自分で作り出したルールが成立しなくなった時に言う口癖。

共感性がなく傲慢なのは「ルールを定める人格」であり、それを除けば主体性も判断力もない非常に薄っぺらい「ルールに従う人格」だけが残る。
自分で定めたルールに正しさを見いだせなくなった自己愛性人格障害者は、従う対象がなくなり何も判断できなくなる。
だから謝罪や反省の場で「なぜそんなことをしてしまったのかわからない」「今後どうしたらいいかわからない」など、はたから見れば無責任、他人事、回避のように見える言動しかできないが本当にわからなくなっているのだ。

彼らにしてみれば無責任や他人事という感覚はなく一貫性がある。
自分の定めた異常なルールに従って行動をする自分、非難されて世間の評価に従って自身の身の振り方をしようとする自分、当人にしてみればどちらも等しく愚直で誠実なのだ。

この「わからない」は、前述の「違うの?」をひとつひとつ懇切丁寧に説いていった先にある。
自分が常識だと思っていたものは間違っていたんだ、そのルールは自分が作り上げていたものだったんだ、と自己愛性人格障害者当人に理解させる必要がある。
一般的に健常者は自己愛性人格障害者に対してそこまで親身にならずに気持ち悪がって離れていくので、この段階を見たことがある人はあまり多くないのではないだろうか。

この状態にまで行くにはかなりの労力がかかり疲弊し、且つ得るものが何もないのでおすすめしない。
自己愛性人格障害と思われる著名人の謝罪会見などを見れば早い。
2020年記憶に新しいところだと、アンジャッシュの渡部氏の謝罪会見がそれに該当するだろう。

似たようなメンバーのコミュニティをよく作り、よく壊す

彼らは細かいコミュニティを多数作る。
全く異なる界隈であれば理解はできるがそうではなく大体同じメンバーで少しずつメンバーを変えてコミュニティを形成する。
そのコミュニティで何か問題が起こるとそのコミュニティを解体に持ち込む。
そして似たメンバーの別コミュニティに活動をシフトしていく、つまりそこに含まれない少数の人を排除する。
もしくは少人数のコミュニティを解体して、その少人数全員が含まれる大きいコミュニティで何食わぬ顔をして存在しているというのも往々にしてある。

ここでいうコミュニティとは、SNSにおける複数アカウントであったり、ラインのグループなど。
彼らは自分のコントロール下に置ける単位のコミュニティを多数作るのだ。
そして彼らは他者と折り合いがつかなくなっても、対個人には関係を崩そうとせずコミュニティを崩壊させる。
崩壊させるとは、捨て台詞を吐いてグループを抜けるなど幼稚な言動が多い。
残された人たちは彼らの去り際の言動に嫌気が差し、そのコミュニティに寄り付かなくなり誰もいなくなる。
そして不快感を与えて去っていったにも関わらず何食わぬ顔で別のコミュニティに現れるのだ。

彼らは複数のコミュニティを支配下に置いて交友関係が広かったとしても、特定の誰かと特別仲が良いということはない。
中心人物のように存在していても誰とも親しくないのだ。
交友関係にある人の大多数が彼らの異常性に気がついていたとしても、それを指摘するような個人的関係性はなく指摘するような決定打もないので誰も何も言わない。
だから問題が表面化しづらい。
しかし彼らからすれば、要領よくやっているので誰にも指摘されない、一目置かれているので誰も言う立場にない、などと思い込んでいる。

安心できるのは共依存の関係か支配できる相手だけ

彼らは共依存の関係にあるか相手をコントロール下に置くかでしか自己の安全性を確保出来ない。
つまり自分に依存していない相手や思い通りに動かせない相手に対して安心できないし、相手に対してどうすれば自分が優位的にいられるかを基準に生きている。
だから常に攻撃性に支配され勝ち負けで物事を見ている。

思い通りに動かせる人の特徴は以下。
・社会経験が浅く自分のルールを突きつけられる人。
・コミュニティを横断するだけの人脈や影響力を持たない人。

つまり社会から孤立させて自分のルールに従わせることの出来る弱者を好む。

こちらが悪者にされないために

彼らはプライドが高い。
それは彼らの言動から一目瞭然なので、空気を読んだ健常者は彼らに対して何か指摘する時に気を使って1対1の状況を作って話をすることがあるがそれはやめた方がいい。
嫌な言い方になるが、第三者のいるところで証人を作るようにした方がいい。
その第三者が彼らの取り巻きに落ちていない限り、多くの場合声に出していないだけであなたと同じように思っている。

彼らは指摘されたことで「あいつは気が狂った、困ったやつだ」などとこちらを異常者のように扱ったり、
もうないなと愛想を尽かしこちらが何も言わず距離を取ったら「あいつは何も言い返せなかった、言い負かしてやった」と扱うなど、
こちらのいないコミュニティで勝手に悪者にされていることも少なくない。
しかし彼らは他者を貶めようと主観で動いている訳ではなく「起こった事実を客観的に言っているだけ」という感覚なのだ。

もしも指摘する必要があるのであれば、彼らへの指摘をする様子も、それに対して彼らが捨て台詞を吐く様子も、第三者が見ているところでやって終わらせたほうがいい。
ただしその第三者が取り巻きだったり該当の自己愛性人格障害者との関わりが浅い人の場合には「何の問題も起きていないのに非難し始めた」「問題を起こした」と映ってしまうので誰を証人に選ぶかは重要である。

しかし一番賢明なのは、はなから関わらないことである。
彼らと直接関わることで得られるものは後にも先にも何もない。
あなたが親でも教師でもない限り、相手を諭してやる必要も義理もないのだ。

自己愛性人格障害者は裸の王様

ここまで書いた内容からわかるように、自己愛性人格障害者は我々健常者とは違うルールで生きている。
彼らは自分で作り出した非常識な自分勝手なルールを常識として、コミュニティの創造と破壊を繰り返すことで都合の良い人だけを選別し周りに置いて生きている。

自己愛性人格障害者というのは、国民が0人の国の王様のようなもの。
童話の「裸の王様」の王様にどうやったら自尊心を傷つけずこちらが危害を被らず何事もなく服を着せることが出来ただろうか?
それが自己愛性人格障害をサポートする立場にある人(家族とか)に求められる振る舞いなのではなかろうか。
「自尊心を傷つけず」というのは「当人が憤慨して拒絶する理由を回避する」という意味だが、そこまで気を使っている時点で人間関係としては既に終わっているのだ。
嘆かわしい…。

日本酒醸造に適した麹を作りたい

去年の末に黄麹菌を買って何度か試したがなんだかうまく行かず。
おかしいなぁと思いつつ半年経ってパッケージに「醤油・赤味噌用」と書いてあることにやっと気づいた。
アホなのか…🤦‍
そこで甘酒用の種麹を書い直して再度やってみたらうまく行った。
一応麹を作ることに成功したので、日本酒醸造の観点から麹について調べた。

注:日本酒醸造に適した麹を作ることは合法だけど、その麹を使って糖化・アルコール発酵させたら違法なのでやらないように。

麹の主な酵素

デンプンを糖に変えるアミラーゼ。
タンパク質をアミノ酸に変えるプロテアーゼ。

・αアミラーゼ
米を液化する。
デンプンをデキストリンに分解する。

・グルコアミラーゼ
デキストリングルコースに分解する。

・酸性プロテアーゼ
タンパク質をペプチドに分解する。

・酸性カルボキシペプチダーゼ
ペプチドをアミノ酸に分解する。

分子がお酒に与える影響

デキストリンは、雑味を感じさせ味を重くする効果がある。(*1)
グルコースは、酵母に分解されアルコール発酵する。
ペプチドは、苦味、えぐみ、雑味を呈する。(*2)
アミノ酸は、味の濃淡や貯蔵時の着色等に関係し、旨味やコク、味幅を与える。多過ぎると雑味。(*3)

きれいな吟醸酒を作りたい場合、グルコアミラーゼの数値が高くそれ以外の数値が低い麹菌が良い。

難消化デキストリンというのはトクホで認められているものらしい。
ということは、高αアミラーゼの麹で日本酒を作ればトクホ飲料になる…?(不明)

(*1)
デキストリン濃度の低い清酒製造技術の開発
https://www.pref.ehime.jp/h30112/sangiken/shokuhin/report/documents/no54_3.pdf

(*2)
平成 20~22 酒造年度東京都産清酒の呈味に関する特徴解析
http://www.food-tokyo.jp/research_record/papers_pdf/paper_8_35_2013.pdf

(*3)
清酒に含まれるアミノ酸の分析について
http://aichi-inst.jp/shokuhin/other/up_docs/news1607-2.pdf

具体的な力価

酒造教本では以下のように定義されている。

分子 酒造場の麹の力価の範囲 望ましい力価
αアミラーゼ 870〜1,700 1,000
グルコアミラーゼ 125〜310 250
酸性プロテアーゼ 2,700〜4,300 4,000
酸性カルボキシペプチダーゼ 3,300〜7,600 -

実際の力価を公開しているメーカーもあったが、吟醸系によく使われる白夜やハイGなどは公開されていないようだった。
福島県のハイテクプラザが公開した白夜の力価は以下。(*4)
測定方法や単位が違うのか全体的に数値が低いが、相対的にグルコアミラーゼが多い。

分子 力価
αアミラーゼ 510
グルコアミラーゼ 272
酸性プロテアーゼ -
酸性カルボキシペプチダーゼ 3,200

種麹・総合微生物スターターメーカー 秋田今野商店
http://www.akita-konno.co.jp/seihin/01.html

清酒用種麹│種麹・種麹もやしから醤油・味噌・清酒・調味料まで
http://www.nihonjouzou.co.jp/products/seishu/

清酒用種麹|種麹(麹菌)の研究・製造 ビオック
http://www.bioc.co.jp/products/tanekoji/seisyu.html

(*4)
短時間製麹における酒質への影響
http://www4.pref.fukushima.jp/hightech/publicity/uploads/rep_rd29.pdf

種麹の形態

種麹には、粉状のものと粒状のものがある。
機械化されている酒蔵では粉状の種麹を使うことも多いらしい。
手作業でやっている酒蔵や吟醸系に使う麹造りの場合は粒状の種麹を使うらしい。
今までいくつか酒蔵を見せてもらった限りでは、粒状のものを使っている印象。

一般家庭用にECサイトで売っている種麹の多くは粉状のもののようだ。
粉状のものは、粒状種麹から胞子のみを回収しそれにα化デンプンなどを加えられている。(*5)
素人でも失敗せず簡単に麹造りが出来るように調合されている模様。

粉状種麹で麹造りしてみた感じ、蒸米の内側に破精るというよりはふわふわで総破精。
極端な話、種麹と配合されたデンプンのふたつで成長し、蒸米はただそれに覆われている感覚。
日本酒作りに向く感じの突き破精にはならなそうな印象。
もしかして蒸米がなくても、粉状種麹に水分と熱を加えればスポンジとかでも破精るのでは…?(不明)

(*5)
種類と形態|種麹(麹菌)の研究・製造 ビオック
http://www.bioc.co.jp/about_tanekoji/type.html

種麹の用途と選び方

甘酒用とされているものは、糖化型でアミラーゼ強め。
味噌・醤油用とされているものは、旨味型でプロテアーゼ強め。
「万能」「幅広い用途」とされているのは中間くらいなのではなかろうか。

マニアックすぎるのか、一般家庭用に販売している粉状種麹は力価が公表されていない。
甘酒に向き、味噌・醤油に不向きとされているものを選ぶ他ない。

かわしま屋で売っている種麹で、力価が公表されているものを一覧にした。

メーカー:秋田今野商店。

商品名 グルコアミラーゼ αアミラーゼ 中性プロテアーゼ 酸性カルボキシペプチダーゼ
白麹雪こまち 470 2,230 85 18,000
醤油1号菌 284 2,057 291 -
豆味噌用 68 375 141 39,100

メーカー:日本醸造工業株式会社。

商品名 プロテアーゼ α-アミラーゼ グルコアミラーゼ グルタミナーゼ
EM-2号菌 ★★★★★ ★★★★☆ ★★★★☆ -
MP-01菌 ★★★☆☆ ★★★☆☆ ★★★☆☆ -
九州麦味噌菌 ★★★☆☆ ★★★★☆ ★★★☆☆ -
特性麦味噌菌 ★★★★★ ★★☆☆☆ ★☆☆☆☆ -
特性豆味噌菌 ★★★★☆ ★★★☆☆ ★★☆☆☆ -
MC-01 ★★★★☆ ★★★☆☆ - ★★★☆☆

酒造教本の数値と相対的に近いのは白麹雪こまち。
でもやっぱり酸性カルボキシペプチダーゼが高めで醸造用にはあまり向かなそう。
ECサイトでは吟醸系の高級酒を作れるようなグルコアミラーゼの高い種麹はおろか、一般的な醸造に適した種麹は売っていないようだ。

種麹(もやし、種菌)の販売 かわしま屋
https://kawashima-ya.jp/?mode=cate&cbid=1528376&csid=1

種麹・総合微生物スターターメーカー 秋田今野商店
http://www.akita-konno.co.jp/seihin/02.html

味噌用種麹│種麹・種麹もやしから醤油・味噌・清酒・調味料まで
http://www.nihonjouzou.co.jp/products/miso/index.html

グルコアミラーゼ力価を上げる製麹方法

・突き破精麹を作るために種麹の散布量を減らす。
・32~35度の温度帯はプロテアーゼが良く出るので、その温度帯を早く通過させる。

多分もっといろいろあるんだろうけど、素人なのでそれ以上のことはよくわからない。

杉錦 生酛(生もと)・山廃造り、純米みりん「飛鳥山」 : こだわりの酒造り
http://suginishiki.com/brewing-pride

現役蔵人が語る。大吟醸の麹造り - その3:仲仕事(なかしごと) - | 日本酒専門WEBメディア「SAKETIMES」
https://jp.sake-times.com/think/study/sake_g_daiginjo_kouji_3

種麹・総合微生物スターターメーカー 秋田今野商店
http://www.akita-konno.co.jp/column/021-030.html

褐変・黒粕の原因と対策

吟醸系に適した麹菌を使うと酒粕が黒くなる。
グルコアミラーゼ高活性株は、同時に高チロシナーゼ活性を示し、麹(最終的には酒粕)を褐変させることが知られている。
チロシナーゼは紫外線を浴びることで活性化し、メラニンをつくり出す。
このメラニンが褐変・黒粕の原因らしい。
グルコアミラーゼ活性が高いとなぜチロシナーゼ活性も高くなるのか、ふたつの関係性は調べてもよくわからなかった。

対策としては以下のいずれか。
・非褐変性の種麹(チロシナーゼを生産しない麹菌)を使う。
・醪にメタ重亜硫酸カリウムを添加する。
・紫外線に当てない。

酒粕は色白がお好み?麹菌がつくるチロシナーゼを発見 | バイオの研究 | 月桂冠総合研究所 | 月桂冠
https://www.gekkeikan.co.jp/RD/bio/bio04/

黒粕防止のためのメタカリ(メタ重亜硫酸カリウム)使用量について | 公益財団法人 日本醸造協会
https://www.jozo.or.jp/topics/%E9%BB%92%E7%B2%95%E9%98%B2%E6%AD%A2%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AE%E3%83%A1%E3%82%BF%E3%82%AB%E3%83%AA%EF%BC%88%E3%83%A1%E3%82%BF%E9%87%8D%E4%BA%9C%E7%A1%AB%E9%85%B8%E3%82%AB%E3%83%AA%E3%82%A6/

二分木検索とルーティングプロトコルを応用した相対的ソートアルゴリズムを考案した

概要

  • ルーティングプロトコルのリンクステートのような感じで、絶対的な数値を持たずに2つのノードを比較して相対的に距離を算出。
  • 各ノードは、前方ノードへアクセスするための左に生える木と、後方ノードへアクセスするための右に生える木を持つ。
  • ノード間の経路はbranch/merge/loopありの1次元情報・有向グラフとして繋げる。
  • なので到達経路が片方向に複数ある場合や、両方向にある場合もある。
  • ノードからノードへの到達経路の探索、ルーティングのMetricのような感じで最短経路の選択。
  • 再帰チェックでルーティングループ回避。

具体的に出来ること

  • じゃんけんのような相対的に勝ち負けが決まるデータのソート。
  • 干支のように延々ループするデータを相対的に次のノードへアクセス。
  • 複数の歯抜け配列から共通項を頼りに順序を保ってひとつの配列にマージ。
  • 左右双方向に繋げば、無向グラフとして全域木の経路探索も可能。

具体例

じゃんけん

leftのノードに負けて、rightのノードに勝つ、という上下関係。
3つのノードはループしているので右にも左にも経路はあるが、もっとも近いノード(隣接しているノード)を直接的な勝敗とする。

root = HighLow::Root.new([
  ["グー", "チョキ"],
  ["チョキ", "パー"],
  ["パー", "グー"],
])

puts "グーからパーへの経路"
routes = root.traceroutes("グー", "パー")
routes.each {|route|
  puts "方向=#{route.direction}, 距離=#{route.metric}, 経路=#{route.nodes.map(&:value)}"
}
puts

left = root.node("グー")
10.times {
  right = left
  left = left.next_lefts.first
  puts "#{right.value}に勝つのは#{left.value}"
}

実行結果。

グーからパーへの経路
方向=left, 距離=1, 経路=["グー", "パー"]
方向=right, 距離=2, 経路=["グー", "チョキ", "パー"]

グーに勝つのはパー
パーに勝つのはチョキ
チョキに勝つのはグー
グーに勝つのはパー
パーに勝つのはチョキ
チョキに勝つのはグー
グーに勝つのはパー
パーに勝つのはチョキ
チョキに勝つのはグー
グーに勝つのはパー
干支

ループするデータ。
leftのノードは過去、rightのノードは未来、という上下関係。
metric(デフォルトではhop数)で何年の差があるかを算出できる。

root = HighLow::Root.new([
  ['', '', '', '', '', '', '', '', '', '', '', '', '']
])

src = ''
dst = ''

left_route = root.left_traceroutes(src, dst).first
right_route = root.right_traceroutes(src, dst).first
shortest_route = root.shortest_routes(src, dst).first

puts "#{src}年を基準に前の#{dst}年は、#{left_route.metric}年前"
puts "#{src}年を基準に次の#{dst}年は、#{right_route.metric}年後"

sign = shortest_route.direction==:left ? '' : ''
puts "#{src}年を基準に一番近い#{dst}年は、#{shortest_route.metric}#{sign}"

実行結果。

卯年を基準に前の申年は、7年前
卯年を基準に次の申年は、5年後
卯年を基準に一番近い申年は、5年後
複数の歯抜け配列から共通項を頼りに順序を保ってひとつの配列にマージ

文殊の知恵的なマージ。
複数のソートされた配列を共通項を元にマージする。
branchなし、loopなしの一番シンプルな例。
一概にソートできない場合は、渡したProcでオーダーを決める。

alphabets = []
alphabets << [:a, :c, :e]
alphabets << [:a, :b, :d]
alphabets << [:c, :d, :e]
alphabets << [:e, :f]
alphabets << [:b, :c, :f]

pp RelativeComparison.merge(alphabets) {|a,b|
  puts "unknown order: #{a} #{b}"
  0
}

実行結果。

[:a, :b, :c, :d, :e, :f]

一概にソートできないbranchありの例。

data = []
data << [:a, :b, :d]
data << [:a, :c, :d]

このような場合だと、以下2つの式が成り立つ。

  • a < b < d
  • a < c < d

bとcの比較は出来ないのでProcが呼ばれる。

出世魚の名前を辿る

地域ごとのブリの稚魚の名前を辿る。

a = []
a << ['アオコ', 'イナダ', 'ワラサ', 'ブリ']
a << ['ワカシ', 'イナダ', 'ワラサ', 'ブリ']
a << ['ワカナゴ', 'イナダ', 'ワラサ', 'ブリ']
a << ['ツバス', 'ハマチ', 'メジロ', 'ブリ']
a << ['ツバイソ', 'フクラギ', 'ガンド', 'ブリ']
a << ['コゾクラ', 'フクラギ', 'ガンド', 'ブリ']
a << ['ツバス', 'ハマチ', 'マルゴ', 'ブリ']
a << ['ツバス', 'ヤズ', 'ワラサ', 'ブリ']


def print_prev_names(node)
  lefts = node.next_lefts
  return if lefts.length==0

  puts "#{node.value}のひとつ前の名前は#{lefts.length}"
  p lefts.map(&:value)
  puts

  lefts.each {|left|
    print_prev_names(left)
  }
end


root = RelativeComparison::Root.new(a)

node = root.node('ブリ')
print_prev_names(node)
puts


puts "ツバスからブリへの経路"
root.traceroutes('ツバス', 'ブリ').each {|route|
  p route.nodes.map(&:value)
}

実行結果。

ブリのひとつ前の名前は4個
["ワラサ", "メジロ", "ガンド", "マルゴ"]

ワラサのひとつ前の名前は2個
["イナダ", "ヤズ"]

イナダのひとつ前の名前は3個
["アオコ", "ワカシ", "ワカナゴ"]

ヤズのひとつ前の名前は1個
["ツバス"]

メジロのひとつ前の名前は1個
["ハマチ"]

ハマチのひとつ前の名前は1個
["ツバス"]

ガンドのひとつ前の名前は1個
["フクラギ"]

フクラギのひとつ前の名前は2個
["ツバイソ", "コゾクラ"]

マルゴのひとつ前の名前は1個
["ハマチ"]

ハマチのひとつ前の名前は1個
["ツバス"]


ツバスからブリへの経路
["ツバス", "ハマチ", "メジロ", "ブリ"]
["ツバス", "ハマチ", "マルゴ", "ブリ"]
["ツバス", "ヤズ", "ワラサ", "ブリ"]

ブリの名前は以下を参照。
http://jsnfri.fra.affrc.go.jp/kids/buri/kw6.html

全域木の経路探索

ノードを双方向に繋いで、無向グラフとする。
metricを指定することでOSPFのようにコストをmetricとすることが出来る。
無指定であればRIPと同じHop数がmetricとなる。

def regist_bi(root, left, right, metric)
  root.regist_pair(left, right, metric: metric)
  root.regist_pair(right, left, metric: metric)
end


root = RelativeComparison::Root.new

regist_bi(root, :left_node, :top_node, 5)
regist_bi(root, :left_node, :center_node, 4)
regist_bi(root, :left_node, :left_bottom_node, 2)
regist_bi(root, :top_node, :center_node, 2)
regist_bi(root, :top_node, :right_node, 6)
regist_bi(root, :center_node, :left_bottom_node, 3)
regist_bi(root, :center_node, :right_bottom_node, 2)
regist_bi(root, :left_bottom_node, :right_bottom_node, 6)
regist_bi(root, :right_bottom_node, :right_node, 4)


shortest_route = root.shortest_routes(:left_node, :right_node).first

puts "hop count: #{shortest_route.hop_count}"
puts "total metric: #{shortest_route.metric}"
puts

puts "routes (metric)"
puts shortest_route.map {|hop|
  "#{hop.node.value} (#{hop.metric})"
}.join(" => ")

実行結果。

hop count: 3
total metric: 10

routes (metric)
left_node (0) => center_node (4) => right_bottom_node (2) => right_node (4)

最短経路アルゴリズムは実装していないが、このローカルにあるこの程度のデータなら何の問題もない。
今後高速化の必要性を感じたらダイクストラ法やら何やらを実装しようと思う。

全域木図は以下を参照。
http://www.deqnotes.net/acmicpc/dijkstra/

あとがき

RubyGemsは気が向いたらやる。

NumericのVALUE埋め込み/ポインタ参照の比較とRationalを用いた高精度な小数演算

今回ちょっと内容が多いからとりあえず目次というか概要というか。
主にこんな話をまとめてる。
VALUEとはいったい何なのか。
VALUE埋め込み表現出来るものとポインタ参照になるもの。
Ruby上でVALUE埋め込みなのかポインタなのかの調べ方。
VALUE埋め込み型とポインタ型の速度比較。
・Rationalで小数演算の精度を高める。
・HpSqrtの精度が高くなった。

VALUEとは

Rubyのオブジェクトを表すVALUEはunsigned longで、そのまま実体として値を持ったりヒープに確保されたメモリのポインタを表したり、持つ値によって意味合いが変わる。

VALUEがそのまま実体として値を持つものはruby_special_constsとして定義されている。
Qfalse,Qtrue,Qnil,Qundefはunsigned long値の一致、FIXNUM,FLONUM,SYMBOLは末尾ビットの一致で表される。
allocが返すポインタは必ず4の倍数である事を利用して、立っているビットを見れば埋め込みかポインタかを判断出来る仕組みになっている。

include/ruby/ruby.h

enum ruby_special_consts {
#if USE_FLONUM
    RUBY_Qfalse = 0x00,     /* ...0000 0000 */
    RUBY_Qtrue  = 0x14,     /* ...0001 0100 */
    RUBY_Qnil   = 0x08,     /* ...0000 1000 */
    RUBY_Qundef = 0x34,     /* ...0011 0100 */

    RUBY_IMMEDIATE_MASK = 0x07,
    RUBY_FIXNUM_FLAG    = 0x01, /* ...xxxx xxx1 */
    RUBY_FLONUM_MASK    = 0x03,
    RUBY_FLONUM_FLAG    = 0x02, /* ...xxxx xx10 */
    RUBY_SYMBOL_FLAG    = 0x0c, /* ...0000 1100 */
#else
    RUBY_Qfalse = 0,        /* ...0000 0000 */
    RUBY_Qtrue  = 2,        /* ...0000 0010 */
    RUBY_Qnil   = 4,        /* ...0000 0100 */
    RUBY_Qundef = 6,        /* ...0000 0110 */

    RUBY_IMMEDIATE_MASK = 0x03,
    RUBY_FIXNUM_FLAG    = 0x01, /* ...xxxx xxx1 */
    RUBY_FLONUM_MASK    = 0x00, /* any values ANDed with FLONUM_MASK cannot be FLONUM_FLAG */
    RUBY_FLONUM_FLAG    = 0x02,
    RUBY_SYMBOL_FLAG    = 0x0e, /* ...0000 1110 */
#endif
    RUBY_SPECIAL_SHIFT  = 8
};

詳しい事はここに書いてあるので割愛。
Ruby 1.7の頃の解説だからFLONUMに関する記述がなかったり若干古いけど、基本的な作りは変わっていない。
http://i.loveruby.net/ja/rhg/book/object.html

flagで使われるbit分、VALUE埋め込みで表せる表現の幅が狭くなる

C言語のlongよりもVALUE埋め込みのIntegerの方が1bit分表現の幅が狭い。
C言語のdoubleよりもVALUE埋め込みのFloatの方が2bit分表現の幅が狭い。

Integerが桁あふれした場合、内部ではstruct RBignumがallocされ、値はbignumとして保持される。
そのためC言語のlongの最大値を超えてメモリの許す限りの大きな数値を扱う事が出来る仕組みになっている。

Floatが桁あふれした場合、内部ではstruct RFloatがallocされ、値はdoubleとして保持される。
(ちなみにバージョンは定かではないが、確かRuby1.9辺りまではFloat値がVALUEに埋め込まれる事はなくすべてヒープメモリに置かれていた。)

なのでRubyのInteger/Floatの演算精度がCよりも劣るという事はない。
しかしVALUEが桁あふれした場合は以下を実行するためにオーバーヘッドが増える。
・オブジェクト生成時にallocによるヒープメモリ確保。
・オブジェクト参照時にポインタ参照。
GCによるヒープメモリ開放。

具体的にどんな値がVALUE埋め込み/ポインタ参照になるのか

internal.hのrb_float_new_inline(double)を元に、doubleからVALUEへの変換するプログラムを作った。

#include <stdio.h>

struct bits {
    unsigned char b1 : 1;
    unsigned char b2 : 1;
    unsigned char b3 : 1;
    unsigned char b4 : 1;
    unsigned char b5 : 1;
    unsigned char b6 : 1;
    unsigned char b7 : 1;
    unsigned char b8 : 1;
};
 
union dbbit {
    double d;
    unsigned long l;
    struct bits b[8];
};

#define RUBY_BIT_ROTL(v, n) (((v) << (n)) | ((v) >> ((sizeof(v) * 8) - n)))

void bits2chars(char *c, struct bits *ba) {
    struct bits *b;
    for (int i=7; 0<=i; i--) {
        b = &ba[i];
        sprintf(c, "%d%d%d%d%d%d%d%d ", b->b8, b->b7, b->b6, b->b5, b->b4, b->b3, b->b2, b->b1);
        c += 9;
    }
    //for (int i=0; i<=7; i++) {
    //    b = &ba[i];
    //    sprintf(c, "%d%d%d%d%d%d%d%d ", b->b1, b->b2, b->b3, b->b4, b->b5, b->b6, b->b7, b->b8);
    //    c += 9;
    //}
    c--;
    *c = '\0';
}

int main(void) {

    union dbbit u;
    struct bits* b;
    char bitschars[80];

    // heap
    u.l = 0;
    u.b[7].b7 = 1;
    u.b[7].b5 = 1;

    // VALUE
    //u.d = 123.0;

    int bits = (int)((u.l >> 60) & 0x7);
    /* bits contains 3 bits of b62..b60. */
    /* bits - 3 = */
    /*   b011 -> b000 */
    /*   b100 -> b001 */

    bits2chars(bitschars, u.b);
    printf("before\n");
    printf("  double: %lf\n", u.d);
    printf("  ulong : %ld\n", u.l);
    printf("  bits  : %s\n", bitschars);
    printf("  flags : %d\n\n", bits);

    printf("after\n");

    if (u.l != 0x3000000000000000 && !((bits-3) & ~0x01)) {
        u.l = (RUBY_BIT_ROTL(u.l, 3) & ~(unsigned long)0x01) | 0x02;

        bits2chars(bitschars, u.b);
        printf("  double: %lf\n", u.d);
        printf("  ulong : %ld\n", u.l);
        printf("  bits  : %s\n", bitschars);
    } else if (u.l==0) {
        printf("  0x8000000000000002\n");
    } else {
        printf("  heap\n");
    }

    return 0;
}

倍精度浮動小数点数のビットパターンはWikipediaを参照。
https://ja.wikipedia.org/wiki/%E5%80%8D%E7%B2%BE%E5%BA%A6%E6%B5%AE%E5%8B%95%E5%B0%8F%E6%95%B0%E7%82%B9%E6%95%B0

doubleをVALUEで表現出来る値

C言語のdouble値123.0をRubyVALUE表現へ変換した結果、RUBY_FLONUM_FLAG(末尾2つのbit 10)が立っている事が確認出来る。

before
  double: 123.000000
  ulong : 4638355772470722560
  bits  : 01000000 01011110 11000000 00000000 00000000 00000000 00000000 00000000
  flags : 4

after
  double: 0.000000
  ulong : 213358032346677250
  bits  : 00000010 11110110 00000000 00000000 00000000 00000000 00000000 00000010

irbで123.0のobject_idを調べると上のプログラムの変換後のulongと一致している事がわかる。
VALUE埋め込みの値なので何度やってもobject_idが変わる事はない。

2.7.0-preview1 :001 > 123.000000.object_id
 => 213358032346677250 
2.7.0-preview1 :002 > 123.000000.object_id
 => 213358032346677250 
doubleをVALUEで表現出来ない値

bit表記でx101、x110、x111から始まるものは桁あふれしてVALUEに埋め込めないのでヒープに置かれる。

before
  double: 231584178474632390847141970017375815706539969331281128078915168015826259279872.000000
  ulong : 5764607523034234880
  bits  : 01010000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
  flags : 5

after
  heap

上の結果でVALUE埋め込み表現出来なかったdouble値をRubyで確認する。
irbでobject_idを調べると毎回変わるのでヒープに置かれているオブジェクトである事がわかる。

2.7.0-preview1 :001 > 231584178474632390847141970017375815706539969331281128078915168015826259279872.000000.object_id
 => 70246480427780 
2.7.0-preview1 :002 > 231584178474632390847141970017375815706539969331281128078915168015826259279872.000000.object_id
 => 70246464940200 
Ruby上でVALUE埋め込みなのかポインタなのか調べる

以下Rubyプログラムでobject_idからVALUE埋め込み型なのかヒープに置かれているのか確認できる。
今回はSymbolについては割愛。

class Object
  RUBY_Qfalse = 0x00      # ...0000 0000
  RUBY_Qtrue  = 0x14      # ...0001 0100
  RUBY_Qnil   = 0x08      # ...0000 1000
  RUBY_Qundef = 0x34      # ...0011 0100

  RUBY_IMMEDIATE_MASK = 0x07
  RUBY_FIXNUM_FLAG    = 0x01  # ...xxxx xxx1
  RUBY_FLONUM_MASK    = 0x03
  RUBY_FLONUM_FLAG    = 0x02  # ...xxxx xx10
  RUBY_SYMBOL_FLAG    = 0x0c  # ...0000 1100
  RUBY_SPECIAL_SHIFT  = 8

  def memory_type
    case object_id
    when RUBY_Qfalse
      "Qfalse"
    when RUBY_Qtrue
      "Qtrue"
    when RUBY_Qnil
      "Qnil"
    when RUBY_Qundef
      "Qundef" # Rubyからは確認出来ない
    else
      if (object_id & RUBY_FIXNUM_FLAG)==RUBY_FIXNUM_FLAG
        "stack integer"
      elsif (object_id & RUBY_FLONUM_MASK)==RUBY_FLONUM_FLAG
        "stack flonum"
      else
        "heap or symbol"
      end
    end
  end
end

p false.memory_type
p true.memory_type
p nil.memory_type
p 123.memory_type
p 0xFFFFFFFFFFFFFFFF.memory_type
p 12.3.memory_type
p 231584178474632390847141970017375815706539969331281128078915168015826259279872.000000.memory_type
p "hello".memory_type

実行結果。

"Qfalse"
"Qtrue"
"Qnil"
"stack integer"
"heap or symbol"
"stack flonum"
"heap or symbol"
"heap or symbol"

実行速度の差

IntegerのVALUE埋め込みとヒープメモリでの速度比較。

require 'benchmark'

repeat = 1000
n = 1000

stack_time = Benchmark.realtime do
  n.times {
    repeat.times.map{|i| 1}.inject(:+)
  }
end

heap_time = Benchmark.realtime do
  n.times {
    repeat.times.map{|i| 0xFFFFFFFFFFFFFFFF}.inject(:+)
  }
end

puts "Stack time : #{stack_time}"
puts "Heap time  : #{heap_time}"
puts "Ratio      : #{heap_time / stack_time}"

速度比較結果。

Stack time : 0.07842599996365607
Heap time  : 0.13512099999934435
Ratio      : 1.7229107701777688

FloatのVALUE埋め込みとヒープメモリでの速度比較。

require 'benchmark'

repeat = 1000
n = 1000

stack_time = Benchmark.realtime do
  n.times {
    repeat.times.map{|i| 123.0}.inject(:+)
  }
end

heap_time = Benchmark.realtime do
  n.times {
    repeat.times.map{|i| 231584178474632390847141970017375815706539969331281128078915168015826259279872.000000}.inject(:+)
  }
end

puts "Stack time : #{stack_time}"
puts "Heap time  : #{heap_time}"
puts "Ratio      : #{heap_time / stack_time}"

速度比較結果。
bignumと違い固定長で中身doubleのため、そこまで極端に遅くはならない。

Stack time : 0.11943600000813603
Heap time  : 0.13702500006183982
Ratio      : 1.1472671560710808

小数演算の精度をもっと高めたい

Float同士の計算は丸め誤差がよく生まれる。
以下の2の平方根の1000倍を求めるプログラムでは、1000を掛けた場合は誤差がなく、1000回足した場合は誤差が生じている。

2.7.0-preview1 :001 > Math.sqrt(2)
 => 1.4142135623730951 
2.7.0-preview1 :002 > Math.sqrt(2)*1000
 => 1414.213562373095 
2.7.0-preview1 :003 > 1000.times.map{|i| Math.sqrt(2)}.inject(:+)
 => 1414.213562373105 

一般的に言えばBigDecimalを使えって話になるが、BigDecimal有理数虚数に対応していない。
平方根を計算する場合は虚数も考慮しなければいけない。
まずはRationalとして計算してみる。
以下の結果から、Rationalに変換してから計算するとBigDecimalや掛け算と同じ結果が得られる事がわかる。

2.7.0-preview1 :016 > f_add = 1000.times.map{|i| Math.sqrt(2).to_r}.inject(:+).to_f
 => 1414.213562373095 
2.7.0-preview1 :017 > 1000.times.map{|i| BigDecimal(Math.sqrt(2).to_s)}.inject(:+).to_f
 => 1414.213562373095 

Rationalは分母VALUE、分子VALUEを持つ。
つまり分母と分子にBignum値を持つ事が出来る。
そして虚数にも対応するためにComplexとする。
Complex(Rational(Bignum, Bignum), Rational(Bignum, Bignum))とすれば、実数/虚数ともに小数値を可変長精度で表せる事になる。
分子Bignum分母Bignumの有理数を実数と虚数に持つ複素数同士の足し算をする事で、有理数にも虚数にも対応した高精度な小数演算を実現する。

2.7.0-preview1 :005 > 1000.times.map{|i| Math.sqrt(Complex(1, 1))}.inject(:+)
 => (1098.6841134678039+455.08986056222534i) 
2.7.0-preview1 :006 > c = 1000.times.map{|i| c = Math.sqrt(Complex(1, 1)); Complex(c.real.to_r, c.imag.to_r)}.inject(:+)
 => ((618504170501439125/562949953421312)+(1024771263224069125/2251799813685248)*i) 
2.7.0-preview1 :007 > Complex(c.real.to_f, c.imag.to_f)
 => (1098.68411346781+455.0898605622274i) 
Rationalを内包するComplexの速度

Floatの足し算とRationalを内包するComplexの足し算の速度比較。

require 'benchmark'

repeat = 1000
n = 1000

f_mul = Math.sqrt(2) * repeat
r_mul = Math.sqrt(2).to_r * repeat

f_add = nil
r_add = nil

f_time = Benchmark.realtime do
  n.times {
    f_add = repeat.times.map{|i| Math.sqrt(2)}.inject(:+)
  }
end

r_time = Benchmark.realtime do
  n.times {
    r_add = repeat.times.map{|i| Math.sqrt(2).to_r}.inject(:+)
  }
end

puts "Float root 2        : #{Math.sqrt(2)}"
puts

puts "Float mul           : #{f_mul}"
puts "Rational mul        : #{r_mul.to_f}"
puts "Diff                : #{f_mul - r_mul}"
puts

puts "Float repeat add    : #{f_add}"
puts "Rational repeat add : #{r_add.to_f}"
puts "Diff                : #{f_add - r_add}"
puts

puts "Float time          : #{f_time}"
puts "Rational time       : #{r_time}"
puts "Ratio               : #{r_time / f_time}"

結果。
約6.35倍かかるようになったけど誤差はなくなった。

Float root 2        : 1.4142135623730951

Float mul           : 1414.213562373095
Rational mul        : 1414.213562373095
Diff                : 0.0

Float repeat add    : 1414.213562373105
Rational repeat add : 1414.213562373095
Diff                : 1.000444171950221e-11

Float time          : 0.1697259999345988
Rational time       : 1.0777420001104474
Ratio               : 6.349893360626763

HpSqrtでRationalを内包するComplexを採用

高精度平方根演算ライブラリHpSqrtでRationalを内包するComplexを採用した。
上記のベンチマークは一番差が出る極端な例で、この変更前後でのTravisCIのユニットテスト実行時間(assertions/s)を見ると1.22倍程度となっている。
バージョンによってassertion数も増えているので一概には比較出来ないが、テストで書いたような使い方をするなら処理速度に問題はないかなと。

1.8.0
Finished in 0.002686s, 2978.0063 runs/s, 18612.5394 assertions/s.

1.10.0
Finished in 0.004189s, 2148.2426 runs/s, 15276.3917 assertions/s.

計算速度は遅くなったけど、その分精度は上がった。
変更前も掛け算の精度は高かったけど、割り算・足し算・引き算の精度は高くなかった。(と言ってもRuby標準と同じ精度の意)
今回の更新で四則演算すべてでRuby標準のものよりも高精度に演算出来るようになった。

遅くなった理由と精度は確かだという事を言いたいだけで書いた技術記事なんで、これ読んだ人は高精度なHpSqrtを使ったらいいと思う。

GitHub: https://github.com/yoshida-eth0/ruby-sqrt
RubyGems: https://rubygems.org/gems/hpsqrt

環境

$ ruby -v
ruby 2.7.0preview1 (2019-05-31 trunk c55db6aa271df4a689dc8eb0039c929bf6ed43ff) [x86_64-darwin18]

$ gem list | grep hpsqrt
hpsqrt (1.10.0)

Numericクラスのinteger?とreal?の使われ方と返すべき値

Numeric.integer?

Numeric.integer?とは

自身が Integer かそのサブクラスのインスタンスの場合にtrue を返します。そうでない場合に false を返します。
Numeric のサブクラスは、このメソッドを適切に再定義しなければなりません。

https://docs.ruby-lang.org/ja/2.6.0/method/Numeric/i/integer=3f.html
Numeric.integer?の使われている箇所

Rubyのソース内を見た感じ、range.cの以下箇所で使われている。

static VALUE range_bsearch(VALUE range)

Rangeの始まりのオブジェクトと終わりのオブジェクトのinteger?がtrueを返す場合にto_intされてIntegerに変換されbsearchの処理がされている。

Numeric.integer?が返すべき値

クラスのオブジェクトが整数値としての表現が出来るか否か。
つまりto_intが正しく整数値を返すように実装されているか否か。
Numeric.to_intはNumeric.to_iを呼び出すので、Numericを直接継承した場合はto_iを実装するでも良い。

クラスごとに固定値を返すべきなのか、オブジェクトごとに変わって良いのかは、実装よりもRubyの思想による部分が大きいのでよくわからない。

実装の観点から言うと、クラス固定でももちろん動くし、オブジェクトごとに返す値が違っても動くし、オブジェクトの状態によって返す値が変わっても動く。
とりあえず現状Range.bsearchでしか使われていないから、小数や虚数が失われる事なくIntegerで表すことが出来るか、という観点でオブジェクトごとに返しても良いかもしれない。

思想の観点から言うと、Rubyで定義されているNumeric.integer?とInteger.integer?はクラスで固定値を返しているのでクラス固定が望ましいのかもしれない。
Numericを継承したクラスはfreezeしてimmutableであるべきで、そもそもオブジェクトの状態が変わるってのはナシかなと思う。

Integerかそのサブクラスのインスタンスかを判定するならkind_of?で良いのでは?は思想的にNG。
ダックタイピングなので、Integerとしての要件を満たすメソッドが定義されていればNumericやIntegerを継承していなくても、それはIntegerである。
Integerとしての動きをするか否かは、型で決めるのではなくオブジェクト自身が申告するべきという思想。

MyNumはIntegerとしての挙動をして、正しくRange.bsearchが動く。
ちなみに、MyNumはNumericを継承していなくても動く。

class MyNum < Numeric

  def initialize(value)
    @value = value
  end 

  def integer?
    true
    #false
  end 

  def to_i
    @value
  end 
  alias_method :to_int, :to_i

  def next
    self.class.new(@value + 1)
  end 
  alias_method :succ, :next

  def <=>(other)
    @value <=> other
  end 

  def coerce(other)
    [other, @value]
  end 
end

ary = [0, 4, 7, 10, 12] 
p (MyNum.new(1)...ary.size).bsearch {|i| ary[i] >= 4 } # => 1
p (1...MyNum.new(ary.size)).bsearch {|i| ary[i] >= 4 } # => 1
p (MyNum.new(1)...MyNum.new(ary.size)).bsearch {|i| ary[i] >= 4 } # => 1

MyNum.integer?がtrueを返す場合の実行結果。
内部ではIntegerとして処理されbsearchが実行出来る。

1
1
1

MyNum.integer?がfalseを返す場合の実行結果。
内部でInteger同士の比較が出来ずに例外が発生する。

Traceback (most recent call last):
	1: from mynum.rb:32:in `<main>'
mynum.rb:32:in `bsearch': can't do binary search for MyNum (TypeError)

Numeric.real?

Numeric.real?とは

常に true を返します。(Complex またはそのサブクラスではないことを意味します。)
Numeric のサブクラスは、このメソッドを適切に再定義しなければなりません。

https://docs.ruby-lang.org/ja/2.6.0/method/Numeric/i/real=3f.html
Numeric.real?の使われている箇所

complex.cの以下関数で使われている。

inline static void nucomp_real_check(VALUE num)
VALUE rb_complex_plus(VALUE self, VALUE other)
VALUE rb_complex_minus(VALUE self, VALUE other)
VALUE rb_complex_mul(VALUE self, VALUE other)
inline static VALUE f_divide(VALUE self, VALUE other, VALUE (*func)(VALUE, VALUE), ID id)
VALUE rb_complex_pow(VALUE self, VALUE other)
static VALUE nucomp_eqeq_p(VALUE self, VALUE other)
static VALUE nucomp_coerce(VALUE self, VALUE other)
static VALUE nucomp_convert(VALUE klass, VALUE a1, VALUE a2, int raise)

具体的に言うと、
・Complexと他のオブジェクトとの四則演算。(+, -, *, /, **)
・Complexと他のオブジェクトとの比較。(==)
・他のオブジェクトとComplexとの比較。(coerce)
・Complexオブジェクトを生成する際の引数チェック。

ダックタイピングどこいった

real?が使われている箇所は全て、Numericのサブクラスであるかも合わせてチェックされている。

if (k_numeric_p(other) && f_real_p(other)) {
  // 〜〜〜
}

Rubyで表すとこう。

if other.kind_of?(Numeric) && other.real?
  # 〜〜〜
end

Numericのサブクラスでありreal?がtrueを返せば、実数値に変換出来て四則演算が出来る、とメソッド以上の意味合いを持たせているように見える。
Numericの継承/実装とreal?の再定義/実装という言い方をすれば、ダックタイピングから外れて、Javaで言うところのinterface/abstract methodのような継承関係が機能を表すような作りになっていると言える。

Complexの四則演算

足し算を例にする。

/*
 * call-seq:
 *    cmp + numeric  ->  complex
 *
 * Performs addition.
 *
 *    Complex(2, 3)  + Complex(2, 3)   #=> (4+6i)
 *    Complex(900)   + Complex(1)      #=> (901+0i)
 *    Complex(-2, 9) + Complex(-9, 2)  #=> (-11+11i)
 *    Complex(9, 8)  + 4               #=> (13+8i)
 *    Complex(20, 9) + 9.8             #=> (29.8+9i)
 */
VALUE
rb_complex_plus(VALUE self, VALUE other)
{
    if (RB_TYPE_P(other, T_COMPLEX)) {
        VALUE real, imag;

        get_dat2(self, other);

        real = f_add(adat->real, bdat->real);
        imag = f_add(adat->imag, bdat->imag);

        return f_complex_new2(CLASS_OF(self), real, imag);
    }
    if (k_numeric_p(other) && f_real_p(other)) {
        get_dat1(self);

        return f_complex_new2(CLASS_OF(self),
                              f_add(dat->real, other), dat->imag);
    }
    return rb_num_coerce_bin(self, other, '+');
}

1つめの条件処理、otherがComplex Typeである場合。
Complex同士の足し算を行う。
実数同士、虚数同士を足し算して、Complex型のオブジェクトを返す。

2つめの条件処理、Numericのサブクラスでありreal?がtrueを返す場合。
otherを実数の数値として扱い足し算を行う。
Complexの実数とotherを足し算、Complexの虚数をそのまま。

いずれの条件にも当てはまらない場合。
otherがNumericではない場合や、other.real?がfalseを返す場合など。
other.coerce(self)が呼び出され同一のクラスに変換してから足し算を行う。

余談。
ここでいう「Complex Type」ってのはクラスとは違う、Cの構造体レベルの定義で、RComplexで表されるオブジェクトを言う。
include/ruby/ruby.hに定義されているruby_value_typeで識別され、実体はinternal.hに定義されてるRComplex。

enum ruby_value_type {
    // 〜〜〜
    RUBY_T_COMPLEX  = 0x0e,
    // 〜〜〜
};
struct RComplex {
    struct RBasic basic;
    VALUE real;
    VALUE imag;
};
Numeric.real?が返すべき値

明示的に以下のように呼ぶ事にする。
実数を持ち虚数を持たないNumericを継承したクラス => MyNum
実数も虚数も両方持つNumericを継承したクラス => MyComp

結論その1。(クラス固定値)
MyNum.real?はtrueを返すべき。
MyComp.real?はfalseを返すべき。

結論その2。(オブジェクト固定値)
MyCompが内包する虚数値が0だった場合trueを、0以外だった場合falseを返しても動く。
しかしその場合MyCompの虚数値によって、Complex + MyCompの戻り値の型が変わる事になる。
MyCompが実数値/虚数値以外の情報を保持いていなくて、Complexが返ってきてもMyCompが返ってきても以降の処理がダックタイピングでどうとでもなる、というなら気にしなくても良いと思うけどドキュメントは気持ち悪くなる。

以下ちぐはぐな実装。

MyComp.real?がtrueを返した場合、Complex + MyCompするとMyCompの虚数は失われる。
MyComp + Complexだと、MyComp.+の実装次第でどうにでも出来る。
しかしオペランドの順序が変わっただけで計算結果が異なるというのは宜しくないので、MyComp.real?はfalseを返すべき。

MyNum.real?がfalseを返した場合、MyNumをComplexに変換すれば虚数は失われずに計算出来るが気持ち悪い。
Complex + MyNumするとMyNum.coerceが呼ばれるのでそこで[Complex, Complex]を返せば虚数は失われずに計算出来る。
MyNum + ComplexするとMyNum.+が呼ばれるので、そこでComplex型のオブジェクトを返す、もしくはComplexを内包したMyNumを返す、とすれば第2オペランド虚数は失われずに計算出来る。
以上のようにComplexに依存した謎実装になってしまう。

クラスが固定値を返す例

以下2つのクラスを定義してComplexとの足し算をする。
・実数型MyNum2
複素数型MyComp

class MyNum2 < Numeric
  def initialize(value)
    @value = value
  end

  def +(other)
    MyNum2.new(@value + other)
  end

  def to_f
    @value.to_f
  end

  def real?
    true
  end

  def coerce(other)
    if other.kind_of?(Integer)
      [other.to_f, self.to_f]
    else
      super
    end
  end
end

class MyComp < Numeric
  attr_reader :real
  attr_reader :imag

  def initialize(real, imag=0)
    @real = real
    @imag = imag
  end

  def +(other)
    MyComp.new(self.real + other.real, self.imag + other.imag)
  end

  def real?
    false
  end

  def coerce(other)
    if other.kind_of?(Complex)
      [MyComp.new(other.real, other.imag), self]
    else
      super
    end
  end
end

p Complex(3, 1) + MyNum2.new(5)     # MyNum2は実数として、Complexの実数と足し算される。
                                    # MyNum2.coerceにComplex.realが渡され、[Float, Float]に変換しその足し算の結果が実数となる。
                                    # 返り値はComplex。
p MyNum2.new(5) + Complex(3, 1)     # MyNum2.+が呼ばれる。返り値はMyNum2。
puts

p Complex(3, 1) + MyComp.new(5, 2)  # MyComp.coerceが呼ばれ[MyComp, MyComp]に変換、MyComp.+が呼ばれる。
p MyComp.new(5,2 ) + Complex(3, 1)  # MyComp.+が呼ばれる。

実行結果。

(8.0+1i)
#<MyNum2:0x00007fa63f140540 @value=(8+1i)>

#<MyComp:0x00007fa63f140310 @real=8, @imag=3>
#<MyComp:0x00007fa63f1401a8 @real=8, @imag=3>

オブジェクトごとに固定値を返す例

MyComp2.imagが0だった場合trueを、0以外だった場合falseを返す複素数クラスMyComp2を定義して足し算をする。

class MyComp2 < Numeric
  attr_reader :real
  attr_reader :imag

  def initialize(real, imag=0)
    @real = real
    @imag = imag
  end

  def +(other)
    MyComp2.new(self.real + other.real, self.imag + other.imag)
  end

  def to_f
    @real.to_f
  end

  def real?
    @imag==0
  end

  def coerce(other)
    if other.kind_of?(Complex)
      [MyComp2.new(other.real, other.imag), self]
    else
      super
    end
  end
end

p Complex(3, 1) + MyComp2.new(5, 2) # クラスが固定値を返す例のMyCompと同じ。返り値はMyComp2。
p Complex(3, 1) + MyComp2.new(5, 0) # MyComp2は実数として扱われComplex.+が呼ばれるため、返り値はComplex。
puts

p MyComp2.new(5, 2) + Complex(3, 1) # クラスが固定値を返す例のMyCompと同じ。返り値はMyComp2。
p MyComp2.new(5, 0) + Complex(3, 1) # MyComp2.+が呼ばれるため、返り値はMyComp2。

実行結果。
返り値の型に注目。

#<MyComp2:0x00007fe84d98cf00 @real=8, @imag=3>
(8.0+1i)

#<MyComp2:0x00007fe84d98ccf8 @real=8, @imag=3>
#<MyComp2:0x00007fe84d98ca00 @real=8, @imag=1>

ちぐはぐな実装の例

class MyNum3 < Numeric
  def initialize(value)
    @value = value
  end

  def +(other)
    MyNum3.new(@value + other)
  end

  def real?
    false
  end

  def coerce(other)
    if other.kind_of?(Complex)
      [other, Complex(@value, 0)]
    else
      super
    end
  end
end

class MyNum4 < Numeric
  def initialize(value)
    @value = value
  end

  def +(other)
    Complex(@value + other)
  end

  def real?
    false
  end

  def coerce(other)
    if other.kind_of?(Complex)
      [other, Complex(@value, 0)]
    else
      super
    end
  end
end

class MyComp3 < Numeric
  attr_reader :real
  attr_reader :imag

  def initialize(real, imag=0)
    @real = real
    @imag = imag
  end

  def +(other)
    MyComp3.new(self.real + other.real, self.imag + other.imag)
  end

  def to_f
    @real.to_f
  end

  def real?
    true
  end

  def coerce(other)
    if other.kind_of?(Complex)
      [MyComp3.new(other.real, other.imag), self]
    else
      super
    end
  end
end

p Complex(3, 1) + MyNum3.new(5)     # MyNum3.coerceが呼ばれ[Complex, Complex]に変換され、Complex.+が呼ばれる。
p MyNum3.new(5) + Complex(3, 1)     # MyNum3.+が呼ばれ、Complexを内包するMyNum3が返る。
puts

p Complex(3, 1) + MyNum4.new(5)     # MyNum4.coerceが呼ばれ[Complex, Complex]に変換され、Complex.+が呼ばれる。
p MyNum4.new(5) + Complex(3, 1)     # MyNum4.+が呼ばれ、Complexが返る。
puts

p Complex(3, 1) + MyComp3.new(5, 2) # Complex.+が呼ばれ、MyComp3の虚数は失われ計算される。
p MyComp3.new(5, 2) + Complex(3, 1) # MyComp3.+が呼ばれ、虚数は失われずに計算される。

実行結果。
MyNum3とMyNum4は返り値の型に注目。
MyComp3は計算結果に注目。

(8+1i)
#<MyNum3:0x00007f9fcd0517b0 @value=(8+1i)>

(8+1i)
(8+1i)

(8.0+1i)
#<MyComp3:0x00007f9fcd050810 @real=8, @imag=3>

まとめ

Numeric.integer?は、小数や虚数、その他の付属情報を丸める事なくInteger値に変換出来るのであればtrueにしておけばいい。
falseを返しても特に困る事はなさそう。
中身がIntegerだろうがFloatだろうが、integer?がtrueを返すオブジェクトがほしいならto_iしてIntegerを返せば良いのでは?くらいには思う。

Numeric.real?がtrueを返す場合、Integer/Floatとの親和性がありComplexなど他から使われる型に適している。プリミティブ感強め。
Numeric.real?がfalseの場合、他の数値型を自身のクラスに変換して統制する前提でゴリゴリに作り込むのに適している。オブジェクト感強め。

なんでこんな事調べたか

平方根丸め誤差なしで計算出来るライブラリを作っていて。
ライブラリ自体は完成はしたけど、今回まとめた内容を元にreal?/integer?の扱いを変えるかも。

GitHub: https://github.com/yoshida-eth0/ruby-sqrt
RubyGems: https://rubygems.org/gems/hpsqrt

環境

$ ruby -v
ruby 2.7.0preview1 (2019-05-31 trunk c55db6aa271df4a689dc8eb0039c929bf6ed43ff) [x86_64-darwin18]

Javaでsynchronizedを再帰的に呼び出した時の挙動

一般的にはやらないだろうけど、気になって調べてみた。
 

ソース

OpenJDK、Hotspot VMをベースに調べてみた。
 

JDK

https://github.com/openjdk-mirror/jdk7u-jdk
commit: f4d80957e89a19a29bb9f9807d2a28351ed7f7df
 

Hostspot VM

https://github.com/openjdk-mirror/jdk7u-hotspot
commit: 50bdefc3afe944ca74c3093e7448d6b889cd20d1
 

Javaソースコードをパースする

何はともあれ、まずはパース。
 
パーサはここらへんに定義されてる。
パーサ自身がJavaで書かれてる。
jdk7u-jdk/src/share/classes/sun/tools/java/Parser.java
Parser::parseStatement()

1260           case SYNCHRONIZED: {
1261             // synchronized-statement: synchronized (expr) stat
1262             long p = scan();
1263             expect(LPAREN);
1264             Expression e = parseExpression();
1265             expect(RPAREN);
1266             return new SynchronizedStatement(p, e, parseBlockStatement());
1267           }

 
どうやらsynchronized以下のブロック(式)はSynchronizedStatementってクラスで表現されている。
jdk7u-jdk/src/share/classes/sun/tools/tree/SynchronizedStatement.java
 

パースされた構文木JVMバイトコードに変換する

Javaソースコードは、パースされてJVMバイトコードに変換される。
 
SynchronizedStatement::code(Environment env, Context ctx, Assembler asm)
バイトコードに変換されるらしい。

136         // lock the object
137         asm.add(where, opc_astore, num1);
138         asm.add(where, opc_aload, num1);
139         asm.add(where, opc_monitorenter);
140
141         // Main body
142         CodeContext bodyctx = new CodeContext(ctx, this);
143         asm.add(where, opc_try, td);
144         if (body != null) {
145             body.code(env, bodyctx, asm);
146         } else {
147             asm.add(where, opc_nop);
148         }
149         asm.add(bodyctx.breakLabel);
150         asm.add(td.getEndLabel());
151
152         // Cleanup afer body
153         asm.add(where, opc_aload, num1);
154         asm.add(where, opc_monitorexit);
155         asm.add(where, opc_goto, endLabel);

 
JVMアセンブリでは、オペコードmonitorenterでオブジェクトをロック、オペコードmonitorexitでオブジェクトをアンロック。
Javaのsynchronizedステートメントは、ロック→ステートメント実行→アンロック、と展開されてバイトコードに変換される。
 
ここで生成されたバイトコードJVMによって実行されるらしい。
その辺は、環境によって実行方法が異なるみたい。
Linuxであればunixsocketを使ってJVMに接続しているようだけど、今回知りたい事とは逸れるからあんまり調べてない。
 

monitorenter/monitorexitの実装

ここからはJVM側のソースを見る。
fast-pathとかslow-pathとか、synchronized修飾子かsynchronizedステートメントかとか、実装がいろいろあって読むのしんどい。
 
jdk7u-hotspot/src/cpu/x86/vm/assembler_x86.cpp
int MacroAssembler::biased_locking_enter(Register lock_reg, Register obj_reg, Register swap_reg, Register tmp_reg, bool swap_reg_contains_mark, Label& done, Label* slow_case, BiasedLockingCounters* counters)
void MacroAssembler::biased_locking_exit(Register obj_reg, Register temp_reg, Label& done)
 
jdk7u-hotspot/src/share/vm/runtime/synchronizer.cpp
void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS)
void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS)
 
jdk7u-hotspot/src/share/vm/runtime/objectMonitor.cpp
void ATTR ObjectMonitor::enter(TRAPS)
void ATTR ObjectMonitor::exit(TRAPS)
 

ObjectMonitor

ObjectMonitorってのは、
オブジェクトのロックをしているThreadを表す _owner と、
monitorenterを何度ネストしているかっていうカウンター _recursions を持つ。(めっちゃ要約)
 
Javaから見るとsynchronizedをネストすると _recursions の値がインクリメントされ、
synchronizedのステートメントを抜けると _recursions の値がデクリメントされる。
_recursions が0になったらアンロックされる。
 

まとめ

synchronizedステートメントやsynchronized修飾子のついたメソッドを再帰的に呼び出しても自分がしたロックで自分自身がデッドロックを起こすような事はない、と。
 
JVM側のくだりになったあたりで力尽きてしまった。
ちゃんと知りたければソース読む方がいい…。
この辺の話題ってJVMを知らないとググろうにもキーワードが解らなかったりしたから、とりあえずJVMへの入り口として一応記事にした。
JVMをちょっとでも知ってからJavaとかScalaとかの言語仕様に迫ると理解が深まるかもネ…?
 

JavaのsynchronizedやJVMのロックに関する参考

Jvm reading-synchronization
https://www.slideshare.net/nminoru_jp/jvm-readingsynchronization
 
モニタ同期・待機処理と再帰ロックサポート - yohhoyの日記
http://d.hatena.ne.jp/yohhoy/20161213/p1
 
synchronizedは実装詳細である - yohhoyの日記
http://d.hatena.ne.jp/yohhoy/20130401/p1
 
同期排他処理 : ロック確保処理 (monitorenter バイトコード, synchronized メソッド)
http://hsmemo.github.io/articles/noOroadKvi.html
 
同期排他処理 : ロック確保処理 : fast-path の処理 : synchronized method のエントリ部の処理 : Template Interpreter での処理
http://hsmemo.github.io/articles/no9662EYy.html
 
同期排他処理 : ロック解放処理 : fast-path の処理 : synchronized method の exit 部 : JVMTI の PopFrame()/ForceEarlyReturn() 時 : Template Interpreter での処理
http://hsmemo.github.io/articles/no3059hIn.html
 
ObjectMonitor クラス関連のクラス (ObjectWaiter, ObjectMonitor)
http://hsmemo.github.io/articles/noBIzmHjmm.html
 
ObjectSynchronizer クラスおよび ObjectLocker クラス (ObjectSynchronizer, ObjectLocker, 及びそれらの補助クラス(ReleaseJavaMonitorsClosure))
http://hsmemo.github.io/articles/noL3z0U0-A.html
 
BasicLock クラスおよび BasicObjectLock クラス (BasicLock, BasicObjectLock)
http://hsmemo.github.io/articles/noBMKPZJux.html