Windows で cmake ポリシー CMP0167 後の boost ライブラリディレクトリの取得方法

Windows で boost と cmake を使っています。以下のように boost のライブラリディレクトリを取得してリンクディレクトリに追加していました。

find_package(Boost 1.87 REQUIRED COMPONENTS program_options)
link_directories(${Boost_LIBRARY_DIRS})

cmake を 3.31 にアップグレードしたところ、CMakeLists.txt で find_package(Boost ...) にて、 boost のライブラリディレクトリが、 cmake の変数 Boost_LIBRARY_DIRS に取得できないという問題が発生しました。

cmake のポリシー CMP0167 に関係するらしく、cmake_policy(SET CMP0167 OLD) で互換モードに設定すれば回避できるのですが、互換モードは cmake の将来のバージョンでは削除されるかもしれないということで他の回避方法を探しました。

cmake 変数 Boost_DIR が BoostConfig.cmake のあるディレクトリのパス(../lib/cmake/Boost-1.xx.0/) を持っているので、その親の親のディレクトリを使用すれば良いという結論になりました。

念のため、その親の親のディレクトリ内の libboost_*.lib にマッチするファイルの数をカウントして 0 でないときだけ採用します。

find_package(Boost ...) の後に以下を追加すれば OK です。

if (WIN32)
    if ("${Boost_LIBRARY_DIRS}" STREQUAL "")
        message(STATUS "Check grandparent of Boost_DIR (${Boost_DIR})...")
        get_filename_component(Boost_PARENT_DIR ${Boost_DIR} DIRECTORY)
        get_filename_component(Boost_GRANDPARENT_DIR ${Boost_PARENT_DIR} DIRECTORY)
        message(DEBUG "Boost_GRANDPARENT_DIR: ${Boost_GRANDPARENT_DIR}")
        file(GLOB FileList "${Boost_GRANDPARENT_DIR}/libboost_*.lib")
        message(TRACE "FileList: ${FileList}") # 長いよ
        list(LENGTH FileList FileCount)
        message(STATUS "libboost file count: ${FileCount}")
        if (FileCount)
            list(APPEND Boost_LIBRARY_DIRS ${Boost_GRANDPARENT_DIR})
            message(STATUS "Boost_LIBRARY_DIRS: ${Boost_LIBRARY_DIRS}")
        endif()
    endif()
    if ("${Boost_LIBRARY_DIRS}" STREQUAL "")
        message(FATAL_ERROR "Boost_LIBRARY_DIRS is empty.")
    endif()
endif (WIN32)

Windows 上の Emacs で M-x compile により、最近の Visual Studio 2022 のコマンドライン版 C++ コンパイラを使用すると文字化けするので対策

Windows 上で Emacs で M-x compile でコンパイルしてます。

Visual Studio 2022 の最近のバージョンではコマンドラインツール出力の文字コーディングが UTF-8 になりました。

CP932 (ShiftJIS) でバッチファイルを組んであり、そこから vsvarsall.bat と msbuild.exe を呼び出すようにしているので、メッセージのコーディングが混在し Emacs では文字化けするようになってしまいました。

その対策です。

以下を ~/.emacs 等に追加して再起動します。

;; Visual Studio 2022 でのビルドのためにコンパイルプロセスのコーディングシステムを切り替える
;;
;; 切り替えは以下の文字列パターンによる
;;
;;   /Visual Studio 20[2-9][0-9]/ (vcvarsall.bat が出力するメッセージ)
;;   /^INFO: The following lines are encoded in utf-8-dos./ (自作スクリプトに埋め込む用)
;;   /^INFO: The following lines are encoded in cp932-dos./ (自作スクリプトに埋め込む用)
;;
;; 切り替えは瞬時には行われないため、直後にメッセージを出力するときは "sleep 1" を実行するなどして
;; 少し待って欲しい。
;;
(defun vs2022-compilation-filter ()
  (let ((process (get-buffer-process "*compilation*")))
    (when (save-excursion
            (goto-char compilation-filter-start)
            (re-search-forward "Visual Studio 20[2-9][0-9]" nil t))
      (set-process-coding-system process 'utf-8-dos 'utf-8-dos))
    (save-excursion
      (goto-char compilation-filter-start)
      (when (re-search-forward "^INFO: The following lines are encoded in " nil t)
        (save-excursion
          (when (re-search-forward "utf-8-dos" nil t)
            (set-process-coding-system process 'utf-8-dos 'utf-8-dos)))
        (save-excursion
          (when (re-search-forward "cp932-dos" nil t)
            (set-process-coding-system process 'cp932-dos 'cp932-dos)))))))
(add-hook 'compilation-filter-hook 'vs2022-compilation-filter)

vcvarsall.bat が出力するメッセージ中の "Visual Studio 2022" に反応して、コンパイルプロセスのコーディングシステムを UTF-8 に切り替えるようにしました。前のバージョンの Visual Studio 2019 も使用しているのですが、こちらは従来通りですので、年号部分は 2020 から 2099 までに反応するように正規表現で指定しています。

また、msbuild の呼び出しから戻り、その後は CP932 でメッセージを出力することがあるため、以下の文字列をバッチファイルから出力することにし、これに反応してコンパイルプロセスのコーディングシステムを CP932 に戻すようにしました。

INFO: The following lines are encoded in cp932-dos.

git stash save -u で保存した stash を消しちゃったときに、そこに入っている untracked なファイルを復元する方法

git stash を消しちゃったときの復元方法は書いてる人がいますが、それだと git stash save -u で保存した untracked なファイルまでは復元できなかったので、復元できる方法を書いておきます。

  1. git fsck --unreachable | grep commit で unreachable な commit の ID を得る。

  2. それらの中から git show --summary <ID> で各 commit の 日付やメッセージを見て探す。沢山あるならツールを駆使して探しましょう。

  3. 見つけたら git cat-file -p <ID> で parent と書いてある ID を取得する。

  4. それらの中から git show --summary <ID> で概要を見て探す。 untracked files on ... というメッセージがあればそれ。ファイル名も分かる。

  5. git show <ID>:<file> で表示できるので、ファイルにリダイレクトすればOK。 Windows の場合は文字コードや改行コードの変換が必要かもしれない。

あなたの log 関数は何ですか?

プログラミング言語などで log が常用対数(底は10)なのか自然対数(底はe)なのかという問題です。

TL;NR

ほとんどのプログラミング言語では log は自然対数、SQL はバラバラなので注意、Excel ワークシートでは常用対数

導入

 \log _{b} x b^{x}逆関数で、この b は底(てい)と呼ばれます。英語では base です。

底は 1 以外の正の数でなければなりませんが、実際に使われるのは 10, e, 2 くらいです。

x は正の数です。複素数にも拡大できますが、多価関数となりややこしくなりますので、ここでは止めておきます。

 \log _{10} は常用対数、 \log _{e} は自然対数と呼ばれます。

この e はネイピア数と呼ばれる定数で、定義は

 e = \lim\limits _{x \to 0} (1+x)^{\frac{1}{x}}

あるいは

 e = \lim\limits _{n \to \infty} (1+\frac{1}{n})^n

で、16桁の近似値は 2.718281828459045 (鮒一鉢二鉢一鉢二鉢至極美味しい)です。

"自然対数の底" という循環定義的な呼び方をされることも多いです。

10 や e の底はよく省略されます。省略されているとき、それが常用対数であるか、自然対数であるかは分野によります

常用対数を log で表したとき、自然対数は ln で表されます。

本題

ほとんどのプログラミング言語では log は自然対数です。FORTRAN, COBOL, Common Lisp, PL/I, C, VBA を確認しました。最近の言語は C に合わせているので、それらも同じです。

常用対数は log10 で用意されていることも、log(x, 10) が使用できる場合もあります。そうでないときでも底の変換公式

 \log _{a} b = \frac{\log _{c} b}{\log _{c} a}

により、log(x) / log(10.0) で計算できます。

例外的なのは Pascal で、自然対数が ln で用意されています。Pascal の先祖の Algol も同様のようです。

一方 Excel ワークシート関数では LOG(x,b) で b の省略時 10 となるため、LOG は常用対数に見えます。自然対数 LN も用意されています。google スプレッドシートでも同じでした。

SQL は統一されてません。注意してください。

  • Oracle SQL では LOG(b,x) で底が先です。
  • MS-SQL では LOG(x) が自然対数で、LOG(x,b) もあります。
  • MySQL では LOG は自然対数です。
  • SQLitePostgreSQL では LOG(x) が常用対数で、LOG(b,x) もあります。

atof の桁数限定高速版

atof をどうしても高速化したくて、桁数を限定すれば高速化できるのではないかと思い、整数部9桁、小数部9桁に限定したものを作ってみました。

9桁というのは int や unsigned int で完全に表現できる最大の桁数です。なるべく整数で演算を行って速度を稼いでいます。

isdigit_fast, pow10n は先に投稿したものを使用しています。

std::atof より 37% 速いという結果が出ています。

実際は 14桁.10桁に対応し、オーバーフローは例外を投げ、さらにコンマをスキップする機能を持ったものを使用しています。

#include "isdigit.h"            // isdigit_fast
#include "pow10n.h"             // pow10n

/// 整数部9桁、小数部9桁までの小数点表記(fフォーマット)文字列を double に変換する
double atof99(const char* str)
{
    int sign = 1;               // 符号
    unsigned int u = 0;         // 整数部
    unsigned int l = 0;         // 小数部
    int lc = 0;                 // 小数部桁数
    while (*str == ' ')
        ++str;
    if (*str == '-') {
        sign = -1;
        ++str;
    }
    while (isdigit_fast(*str)) {
        u = u * 10 + *str - '0';
        ++str;
    }
    if (*str == '.') {
        ++str;
        while (isdigit_fast(*str)) {
            l = l * 10 + *str - '0';
            ++lc;
            ++str;
        }
    }
    return sign * (u + l * double(pow10n(-lc)));
}

P.S. lc は小数部桁数の -1倍にした方が neg 1個分速そうな気がしてきた。

10 の整数乗

10 の整数乗を求める関数で、[-32, 31] 内の int n に対して高速なものを作りました。

if ((np32 & ~63) == 0) のところですが、普通は if (0 <= np32 && np32 <= 63) と書くところです。後者だと cmp (整数比較)と条件ジャンプの組2つにコンパイルされるのに対し、前者は test (ビットand)と条件ジャンプの組1つで済みます。ちょっとでも速くしたかったので、前者を使用しています。テーブルの範囲もビット演算1回で済むように決めました。

コンパイラが最適化してくれてもいいと思うのですが、Visual Studio 2019 の C++ ではやってくれませんでした。もしかしたらコンパイルオプションで指定できるのかもしれません。

// <pow10n.h>

#ifndef pow10n_h
#define pow10n_h

#include <cmath>

//------------------------------------------------------------
//
// pow10n - 10 の整数乗
//

/// [-32, 31] 内の int n に対する pow(10, n) のテーブル
extern const double pow10n_table[31-(-32)+1];

/// [-32, 31] 内の int n に対して高速な 10 の整数乗
inline double pow10n(int n)
{
    extern const double pow10n_table[31-(-32)+1];
    int np32 = n + 32;
    if ((np32 & ~63) == 0) // (0 <= np32 && np32 <= 63)
        return pow10n_table[np32];
    return std::pow(10, n);
}

//------------------------------------------------------------

#endif // pow10n_h

// end of <pow10n.h>
// <pow10n.cpp>

#include "pow10n.h"

//------------------------------------------------------------
//
// pow10n - 10 の整数乗
//

// [-32, 31] 内の int n に対する pow(10, n) のテーブル
const double pow10n_table[31-(-32)+1] = {
    1e-32,
    1e-31,
    1e-30,
    1e-29,
    1e-28,
    1e-27,
    1e-26,
    1e-25,
    1e-24,
    1e-23,
    1e-22,
    1e-21,
    1e-20,
    1e-19,
    1e-18,
    1e-17,
    1e-16,
    1e-15,
    1e-14,
    1e-13,
    1e-12,
    1e-11,
    1e-10,
    1e-9,
    1e-8,
    1e-7,
    1e-6,
    1e-5,
    1e-4,
    1e-3,
    1e-2,
    1e-1,
    1e+0,
    1e+1,
    1e+2,
    1e+3,
    1e+4,
    1e+5,
    1e+6,
    1e+7,
    1e+8,
    1e+9,
    1e+10,
    1e+11,
    1e+12,
    1e+13,
    1e+14,
    1e+15,
    1e+16,
    1e+17,
    1e+18,
    1e+19,
    1e+20,
    1e+21,
    1e+22,
    1e+23,
    1e+24,
    1e+25,
    1e+26,
    1e+27,
    1e+28,
    1e+29,
    1e+30,
    1e+31,
};

//------------------------------------------------------------

// end of <pow10n.cpp>

高速数字判定

isdigit が inline 展開されないので、ASCII の数字のみ判定する関数を作りました。

// <isdigit.h>

#ifndef isdigit_h
#define isdigit_h

#include <stdint.h>

//------------------------------------------------------------
//
// 高速数字判定
//

/// 高速数字判定のテーブル
extern const uint8_t isdigit_fast_table[0x100];

/// 高速数字判定
inline bool isdigit_fast(unsigned char c)
{
    return isdigit_fast_table[c];
}

//------------------------------------------------------------

#endif // isdigit_h

// end of <isdigit.h>
// <isdigit.cpp>

#include "isdigit.h"

//------------------------------------------------------------
//
// 高速数字判定
//

// 高速数字判定のテーブル
const uint8_t isdigit_fast_table[0x100] = {
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
};

//------------------------------------------------------------

// end of <isdigit.cpp>