HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

【注:ネタ】Ruby言語における整数型の最大値と最小値の変遷(しません)

諸事情でかくかくしかじか、とにかく飲み込んでくれ、という状況なので、まぁ今のうちに2019年を振り返っておきますか、ということで。

(※先に断っておきますと、「子どもじゃあるまいし・・」という突っ込みをしたいと思いますが、『心は永遠の幼児』ですので。)

振り返ってみると、面白い画像が出てきました。

f:id:hhelibex:20191224232635p:plain

まさかねぇ、とツイート検索してみたら、未だに残ってるんですねぇ(2019/12/25 00:22現在)‥ということで、以下が元ツイート。

これの参照元の記事は以下。

hhelibex.hatenablog.jp

単に整数型の最大値と最小値を調べているだけなんですけどね。確かに、2017年にRuby 2.0.0というバージョンは「古いもの」なのでしょうが、CentOS 7のベースリポジトリに含まれる正統派バージョンなのですよね。

Ruby 2.0.0というバージョン番号に脊髄反射しただけ」なのか、「突っ込む先を間違えただけ」なのか、「全世界35兆人(!?)のCentOSerを敵に回したいだけ」なのか、はたまた「常に新しいバージョンを実運用で維持し続けられるほどのヒ・マ・人」なのか、まぁ真意は知りませんけど。

Rubyがバージョンによって整数型の最大値と最小値が違うクソ言語ではないことを願って、時間もないので、一気に行ってみましょうか。

使うプログラムは前回のものをちょっと弄って以下です。

if defined?(RUBY_DESCRIPTION)
    print RUBY_DESCRIPTION,"\n"
else
    print RUBY_VERSION,"\n"
end
a = 1
ct = 255
a <<= ct
while ct < 256 && (a << 1) + 1 > a
    a <<= 1
    a += 1
    ct += 1
end
print "    int:max? = ",a,"\n"
a += 1
print "          +1 = ",a,"\n"

a = -1
ct = 255
a <<= ct
while ct < 256 && (a << 1) < a
    a <<= 1
    ct += 1
end
print "    int:min? = ",a,"\n"
a -= 1
print "          -1 = ",a,"\n"

ウザくなるので、256ビットシフト位で止めておいてあげましょうか(十分ウザい)。

では結果。

続きを読む

Apache httpd 2.4用mod_cidr_lookupパッチ

ものすごく今更感はありますが。

CentOS 6⇒7で、Apache httpdが2.2.15⇒2.4.6に変わっています。

そんなわけで、とりあえず、CentOS 7でApache httpd用に作られたmod_cidr_lookupをインストールするためのパッチを投げておきますね。

curl -O -L http://downloads.sourceforge.net/modcidrlookup/mod_cidr_lookup-1.2.tar.gz
tar zxf mod_cidr_lookup-1.2.tar.gz
cd  mod_cidr_lookup-1.2/apache2
cp -vp mod_cidr_lookup.c mod_cidr_lookup.c.bak
cat <<__EOD__ | patch -u -o mod_cidr_lookup.c mod_cidr_lookup.c.bak -
--- mod_cidr_lookup-1.2/apache2/mod_cidr_lookup.c
+++ mod_cidr_lookup-1.2/apache2/mod_cidr_lookup.c
@@ -368,7 +368,7 @@
   apr_sockaddr_t *sockaddr;
   uint8_t        *addr;
 
-  sockaddr = r->connection->remote_addr;
+  sockaddr = r->connection->client_addr;
 
 #if APR_HAVE_IPV6
   if (sockaddr->family == AF_INET6 &&
__EOD__
make
make install

単に/usr/include/httpd/httpd.hで定義されているconn_rec構造体のメンバー変数の名前が変わっただけなんですが。

複数の環境でExcelからPDFファイルを出力するとズレが生じる

ものすごく久しぶりになってしまい、気付けば2019年もあと2か月を切ってしまいました。
まぁ、とりあえず生きています。

さて、ある時からずーっと、Excelファイルで帳票を作ってPDFファイルに変換(「名前を付けて保存」でPDFを選択)すると、環境によってズレが生じてしまうという問題に悩まされてきました。 普段のオフィスでの出力と、本社オフィスに行ったときの出力がズレてしまうという感じですね。
原因もよく分からず、過去の出力とのズレをちまちまと調整しながらしのいでいました。

Microsoftのサイトでは、以下のようなページがあります。

『適用対象: Microsoft Office Excel 2007Microsoft Office Excel 2003』ということなので、古い情報ではあるのですが、私が悩んできた、現時点のExcel 2016でも同じようでした。

『簡単に考えられる解決策は、環境をまったく同じにすることです。』とあるのですが、同じPCで出力してるんだよ!と流してきました。

ところがある日、何がきっかけだったかは完全に忘れましたが、普段のオフィスで出力したときにズレたんですね、きっと。 そこで上記のページのことを思い出し、読み直してみると、「プリンタドライバ」とあります。
ここからはWindows 10をベースとした話になりますが、私がPDFファイルへの変換を行った環境が3パターンあったんですね。

  1. 普段のオフィスで、個人PCから仕事PCにリモートデスクトップ接続で入って作業する
  2. 本社オフィスで、仕事PCを直接操作して作業する
  3. 普段のオフィスで、仕事PCを直接操作して作業する

そこではたと気付いてプリンタの設定を開いてみました。

  • [スタート]メニュー⇒[設定]⇒[デバイス]⇒[プリンターとスキャナー]

すると、下の方に「Windows で通常使うプリンターを管理する」というチェックボックスがあり、それがオンになっています。 試しに、上記のパターンでExcel等で印刷を試してみると・・・

  • リモートデスクトップで入ると、「Canon xxxx (リダイレクト2)」が既定になっている
  • 普段のオフィスで直接操作すると、「Canon xxxx」が既定になっている
  • 本社オフィスに行くと、「Canon yyyy」が既定になっている

という感じで、要はプリンタドライバが違うんですね。

ということで、試しに「Windows で通常使うプリンターを管理する」のチェックを外し、手動で既定のプリンタを選択してみます。 操作は、

  • 対象のプリンタをクリックして[管理]⇒[既定として設定する]

です。

これでExcelからPDFファイル保存をすると、既定のプリンタをどれにするかによって見事にズレが生じました。
過去に出力したときに使ったはずのプリンタドライバを既定に設定して、異なる環境で再出力してみると、見事に一致!PDFファイルとして保存するときには「既定のプリンター」を使っているんですね。
ようやく大きな原因となっているものが特定できました。

・・・長かった orz・・・

でも、ちょっと待ってくださいよ。作業を社内で共有できるようにと、ExcelからPDF出力する際には必ず本社オフィスのプリンタを既定にしてから行うようにしたとしても、そのプリンタが入れ替わっちゃったら・・・

まだ悩みは尽きないようです。別の方法も模索しないとダメかなぁ・・・

C++版の文字列split実装の罠

C++ 文字列 カンマ区切り」とかで検索するとヒットする記事等には、以下のようなコードがよく書かれている。(main関数は動作確認のために自分で付け足したもの)

#include <iostream>
#include <string>
#include <vector>
#include <sstream>

using namespace std;

vector<string> split(string str, char delim) {
    vector<string> res;

    stringstream ss(str);
    string buf;
    while (getline(ss, buf, delim)) {
        res.push_back(buf);
    }

    return res;
}

int main(int argc, char** argv) {
    string line;
    while (getline(cin, line)) {
        vector<string> v = split(line, ',');
        for (int i = 0; i < v.size(); ++i) {
            cout << v[i] << endl;
        }
    }

    return EXIT_SUCCESS;
}

上のコードでも多くのケースでは問題がないのだろうけど、このコードには以下の欠陥がある。

  • strが空文字列だった場合、返されるvectorは空になる
  • strdelimで区切った最後のトークンが空文字列だった場合、返されるvectorは期待されるサイズより1つ小さくなる

これらはいずれも、残りが空文字列になった時点でwhileループを抜けてしまうからである。まぁ考えれば当然だわな。

なので、ちゃんと文字列分割を実装しようと思ったら、上記のケースを別扱いして対応する必要がある。その対応を入れたコードが以下になる。

#include <iostream>
#include <string>
#include <vector>
#include <sstream>

using namespace std;

vector<string> split(string str, char delim) {
    vector<string> res;

    if (str.length() == 0) {
        res.push_back("");
    } else {
        stringstream ss(str);
        string buf;
        while (getline(ss, buf, delim)) {
            res.push_back(buf);
        }
        if (str[str.length() - 1] == delim) {
            res.push_back("");
        }
    }

    return res;
}

int main(int argc, char** argv) {
    string line;
    while (getline(cin, line)) {
        vector<string> v = split(line, ',');
        for (int i = 0; i < v.size(); ++i) {
            cout << v[i] << endl;
        }
    }

    return EXIT_SUCCESS;
}

1つ目のケースは、strが空文字列なのだから、長さは当然0になるので、その判定がtrueになる場合は空文字列1つを入れたvectorを返す。

2つ目のケースは、末尾のトークンが空文字列なのだから、strの最後の文字がdelimに等しくなるので、その判定がtrueになる場合は空文字列をvectorの末尾に追加する。

sortの比較関数の実装ではまった

最初、以下のような実装をしていた。

#include <algorithm>
#include <iostream>

using namespace std;

class ValList {
public:
    vector<int> valList;
    ValList(vector<int> &valList) {
        this->valList = valList;
    }
};

class Comp {
public:
    bool operator()(const ValList *v1, const ValList *v2) const {
        for (int i = 0; i < min(v1->valList.size(), v2->valList.size()); ++i) {
            if (v1->valList[i] < v2->valList[i]) {
                return true;
            }
        }
        return false;
    }
};

int main(int argc, char** argv) {
    int n = 20;
    vector<ValList*> v;
    for (int i = 0; i < n; ++i) {
        vector<int> v1;
        v1.push_back(i);
        v1.push_back(n - i);
        v.push_back(new ValList(v1));
    }

    sort(v.begin(), v.end(), Comp());
}

これを実行すると、なぜか「Segmentation fault」が発生する。なぜ?と思ってCompクラスに以下のようなデバッグコードを入れてみたところ・・・

class Comp {
public:
    bool operator()(const ValList *v1, const ValList *v2) const {
if (v1 == NULL) {
cerr << "v1 is null" << endl;
}
if (v2 == NULL) {
cerr << "v2 is null" << endl;
}
        for (int i = 0; i < min(v1->valList.size(), v2->valList.size()); ++i) {
            if (v1->valList[i] < v2->valList[i]) {
                return true;
            }
        }
        return false;
    }
};
v1 is null
Segmentation fault

と、比較関数に渡されている参照がNULLになっている‥

いろいろ調べて、比較関数は「strict weak ordering」になっていなければならないというのは意識していたのだが、原因がさっぱり分からず‥

で、いろいろデバッグ出力をしながら考えていたら、ふと気づいた。v1 < v2v2 < v1が同時に成り立つことがある実装になっていることに‥

ソートしようとしているデータは以下のようになっている。

[0, 20]
[1, 19]
 :

なので、例えばv1 = [0, 20]v2 = [1, 19]を比較すると、v1[0] < v2[0](0 < 1)かつv2[1] < v1[1](19 < 20)なので、「strict weak ordering」になっていない。

こういう比較をやるときは、以下の様に実装しなければならない。

class Comp {
public:
    bool operator()(const ValList *v1, const ValList *v2) const {
        for (int i = 0; i < min(v1->valList.size(), v2->valList.size()); ++i) {
            if (v1->valList[i] != v2->valList[i]) {
                if (v1->valList[i] < v2->valList[i]) {
                    return true;
                } else {
                    return false;
                }
            }
        }
        return false;
    }
};

つまり、今回の場合だと、先頭の要素から順に比較して、互いに等しくない要素が現れた時点でtruefalseを返さなければならない、ということ。

これに悩んで2時間もはまっていたがな‥

Nを表すのに必要な最低限のビット数

Javaのあるプログラムで非負整数Nに対する以下のような式を見かけた。

int n = (int)(Math.log10(N) / Math.log10(2) + 1.0);

最初、何を求めているのかしばらく分からなかったのでメモ。

ちなみに以下のように書いても同じ(はず)である。

int n = (int)Math.ceil(Math.log10(N) / Math.log10(2));

上記の答えnが何を表すかというと、タイトルに書いてしまったが、Nを2進数にしたときの長さ、つまりビット数の最小値を求めている。

なぜこれでビット数が求められるのかの証明と、他の求め方を記録しておく。

対数を使った求め方の証明

分かってしまえば簡単な話なのだが、以下のようにして証明できる。

非負整数Nを表現するのに必要なビット数をpとすると、
{\displaystyle
2^p > N
}
が成り立つ。この両辺の常用対数を取ると、
{\displaystyle
log_{10}{2^p} > log_{10}{N}
}
{\displaystyle
p*log_{10}{2} > log_{10}{N}
}
{\displaystyle
p > \frac{log_{10}{N}}{log_{10}{2}}
}
右辺は小数になるので、
{\displaystyle
p = \lceil \frac{log_{10}{N}}{log_{10}{2}} \rceil
}

他の求め方

他の求め方としては、以下の2通りがある。

int n = Long.bitCount(Long.highestOneBit(N) - 1) + 1;
int n = Long.toBinaryString(N).length();

1つ目の方法の説明は以下の通り。

  1. まずLong.highestOneBit(N)で最上位のビットのみを残した値を求める。
  2. そこから1を引くと、最上位のビットが0になり、代わりにそれ以下のビットが全て1となる。
  3. Long.bitCount()で1となっているビット数をカウントする。
  4. 最上位のビットの分の1を足す。

2つ目の方法は、説明するまでもないと思うが、単純にNを2進数文字列に変換してその長さを取得しているだけ。 ただし、この方法は他の2つの方法に比べてStringオブジェクトを生成している分だけメモリコストや時間的コストがかかるので、シビアな状況においては要注意。

Range同士の関係を判定する述語についてまとめてみる

非常につまらないミスをやらかして凹んでいるので、反省の意味を込めて、Rangeに対して実装できる述語を整理してみるテスト。

ちなみに、Rangeとは開始と終了(begin & end、from & to、left & right、etc.)の範囲を表現することができる何か。

Javaでクラスとして実装すると以下のような感じだろうか。

public class Range {
    private long begin;
    private long end;
    public Range(long begin, long end) {
        // 面倒なので、順序が逆の場合は単にエラーにしておく。
        if (begin > end) {
            throw new RuntimeException("begin > end");
        }
        this.begin = begin;
        this.end = end;
    }
    public long getBegin() {
        return begin;
    }
    public long getEnd() {
        return end;
    }
}

さて、これに述語をメソッドとして追加していきたいのだが、その前に「述語」って何?って話からしていくと、要はそのRangeが他のRangeと被ってるの?含んでるの?等しいの?とかそういうことである。

述語として考えられるものは、Rangeが表す意味によって2系統に分けられる。

  • Rangeの中にendを含むケース
  • Rangeの中にendは含まないケース

つまり、new Range(1, 10)としたときに、10をRange(範囲)に含むか含まないかということである(各プログラム言語でRangeがどう扱われるかについては、以前に「rangeによるrangeの違い - HHeLiBeXの日記 正道編」でまとめたので、興味があれば参考にしてください)。

で、それぞれのケースに於いて、どんな述語が考えられるかを列挙したのが以下である。

  • Rangeの中にendを含むケース
    • overlaps
    • precedes
    • succeeds
    • contains
    • equals
  • Rangeの中にendは含まないケース
    • overlaps
    • meets
    • precedes
    • succeeds
    • contains
    • equals

こんなところだろうか(ほかにあったら教えて・・)。

「Rangeの中にendは含まないケース」にだけ「meets」というのがあるが、これはendがexclusiveだからこその述語である。これらは「Rangeの中にendを含むケース」では「overlaps」に内包されるものである(beginとendが例えば整数値しか取らないようなケースでは、「range1.end + 1 == range2.begin」は「meets」じゃないのか?という議論もあるが、それはここでは考えないことにする)。

では順に見て行こう‥と言いたいところだが、上記の順で見ていくと説明が二重になりそうなので、述語ごとに見ていくことにする。

それぞれの述語の説明において、パターンを図示し、最終的に判定式にまとめ上げていく。

なお、それぞれの図示の中での記号の意味は以下の通りである。

  • 「●」はその値をRangeに含むこと
  • 「○」はその値をRangeに含まないこと
  • 「━」は「range1」の範囲に含まれる値
  • 「─」は「range2」の範囲に含まれる値
  • 左側の「●」はbeginを表す
  • 右側の「●」または「○」はendを表す

全パターンの列挙

まずは、「range1」と「range2」の位置関係について、全パターンを列挙してみようと思う。

  • Rangeの中にendを含むケース
           ●━━━━━━━━●
(01)●───●
(02)●──────●
(03)●───────────●
(04)●───────────────●
(05)●──────────────────────●
(06)       ●────●
(07)       ●────────●
(08)       ●───────────────●
(09)            ●───●
(10)            ●──────────●
(11)                ●──────●
(12)                   ●───●
  • Rangeの中にendは含まないケース
           ●━━━━━━━━○
(01)●───○
(02)●──────○
(03)●───────────○
(04)●───────────────○
(05)●──────────────────────○
(06)       ●────○
(07)       ●────────○
(08)       ●───────────────○
(09)            ●───○
(10)            ●──────────○
(11)                ●──────○
(12)                   ●───○

overlaps

overlapsとは、一言で言うと、2つのRange間に重なりがあるかどうかの判定である。 列挙した全パターンのうち、overlapsに該当するのはそれぞれ以下のとおりである。

  • Rangeの中にendを含むケース
           ●━━━━━━━━●
(02)●──────●
(03)●───────────●
(04)●───────────────●
(05)●──────────────────────●
(06)       ●────●
(07)       ●────────●
(08)       ●───────────────●
(09)            ●───●
(10)            ●──────────●
(11)                ●──────●

全パターンから除外されたのが以下の2パターンである。

  • range2.end < range1.begin
  • range1.end < range2.begin

つまり、それ以外は「overlaps」とみなされるので、以下のような条件式で判定できる。

    public boolean overlaps(Range that) {
        return this.begin <= that.end && that.begin <= this.end;
    }
  • Rangeの中にendは含まないケース
           ●━━━━━━━━○
(03)●───────────○
(04)●───────────────○
(05)●──────────────────────○
(06)       ●────○
(07)       ●────────○
(08)       ●───────────────○
(09)            ●───○
(10)            ●──────────○

全パターンから除外されたのが以下の4パターンである。

  • range2.end < range1.begin
  • range2.end = range1.begin
  • range1.end = range2.begin
  • range1.end < range2.begin

注意してほしいのは、endはRangeに『含まれない』ということである。

まとめると、以下のような条件式で「overlaps」を判定できる。

    public boolean overlaps(Range that) {
        return this.begin < that.end && that.begin < this.end;
    }

meets

「meets」とは、range1がrange2にちょうど接続するかどうか(連続した1つのRangeにマージできるかどうか)を判定する述語である。

ものによっては「metBy」という述語を入れる場合もあるが、「range1.meets(range2) == range2.metBy(range1)」なのでここでは考えない。

  • Rangeの中にendを含むケース

先にも述べたように、これは「overlaps」に内包されると考えるので、このケースでの「meets」は無いとみなす。

  • Rangeの中にendは含まないケース
           ●━━━━━━━━○
(11)                ●──────○

見ての通り、1パターンしかないので、条件式は以下のようになる。

    public boolean meets(Range that) {
        return this.end == that.begin;
    }

precedes

「precedes」とは、range1がrange2より前に位置するかどうかを判定する述語である。

  • Rangeの中にendを含むケース
           ●━━━━━━━━●
(12)                   ●───●

見ての通り、1パターンしかないので、条件式は以下のようになる。

    public boolean precedes(Range that) {
        return this.end < that.begin;
    }
  • Rangeの中にendは含まないケース
           ●━━━━━━━━○
(12)                   ●───○

見ての通り、1パターンしかないので、条件式は以下のようになる。

    public boolean precedes(Range that) {
        return this.end < that.begin;
    }

succeeds

「succeeds」とは、range1がrange2より後に位置するかどうかを判定する述語である。

  • Rangeの中にendを含むケース
           ●━━━━━━━━●
(01)●───●

見ての通り、1パターンしかないので、条件式は以下のようになる。

    public boolean succeeds(Range that) {
        return this.begin > that.end;
    }
  • Rangeの中にendは含まないケース
           ●━━━━━━━━○
(01)●───○

見ての通り、1パターンしかないので、条件式は以下のようになる。

    public boolean succeeds(Range that) {
        return this.begin > that.end;
    }

contains

「contains」とは、文字通りrange1がrange2の範囲を完全に含むかどうかを判定する述語である。

  • Rangeの中にendを含むケース
           ●━━━━━━━━●
(06)       ●────●
(07)       ●────────●
(09)            ●───●

条件式は以下のようになる。

    public boolean contains(Range that) {
        return this.begin <= that.begin && that.end <= this.end;
    }
  • Rangeの中にendは含まないケース
           ●━━━━━━━━○
(06)       ●────○
(07)       ●────────○
(09)            ●───○

条件式は以下のようになる。

    public boolean contains(Range that) {
        return this.begin <= that.begin && that.end <= this.end;
    }

equals

「equals」とは、言うまでもなくrange1とrange2が同じ範囲を表しているかどうかを判定する述語である。

  • Rangeの中にendを含むケース
           ●━━━━━━━━●
(07)       ●────────●

条件式は以下のようになる。

    public boolean equals(Range that) {
        return this.begin == that.begin && this.end == that.end;
    }
  • Rangeの中にendは含まないケース
           ●━━━━━━━━○
(07)       ●────────○

条件式は以下のようになる。

    public boolean equals(Range that) {
        return this.begin == that.begin && this.end == that.end;
    }