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>

Windows で rsync (cygwin使用)

ディレクトリの同期に便利な rsync ですが、
以前は Windows でも sshd の動いているホストに対して cygwin (https://www.cygwin.com/) の rsync が使えていました。

それがいつの間にかコマンドプロンプトやバッチファイルから使えなくなっていました。

仕方がないので、いちいち cygwin terminal を開いて使っていましたが、
原因と回避策が見つかったので書いておきます。

原因は、

C:\Users\perce-neige>where ssh
C:\Windows\System32\OpenSSH\ssh.exe
C:\tools\cygwin64\bin\ssh.exe

Windows 10 にいつの間にか独自の OpenSSH が入り、その ssh.exe が優先して rsync から使われているためでした。
cygwinssh.exe とは互換性無いようです。

環境変数 PATH の中でディレクトリの順番が

PATH=...;C:\Windows\System32\OpenSSH;...;C:\tools\cygwin64\bin;...

のようになってるせいでもあります。これを変えても動くとは思いますが、それでは他に悪影響がありそうで怖い。

そこで回避策、 cygwinrsync が使用するべき ssh をオプション --rsh=/usr/bin/ssh で指定します。
rsync を使うときに

rsync -rutvhp --rsh=/usr/bin/ssh src-path/ host:/dest-path/ --progress

のようにします。

なお、この ssh に設定ファイルが必要な場合は、cygwin の ~ (ホームディレクトリ) に .ssh ディレクトリを作って入れます。
cygwin のインストール先が C:\tools\cygwin64 ならば

C:\tools\cygwin64\home\perce-neige\.ssh\

とかになるはずです。

ここに config, id_rsa か id_ed25519 等のファイルを入れます。config の書き方などは ssh config などでググってください。

Windows の Emacs の shell-mode で git commit --amend や git rebase -i するときに Emacs で編集したい

普通の commit なら C-x v v で Emacs の中で編集できるんですが、git commit --amend したい場合など、shell-mode 内だとエディタが起動できないってエラーになります。

そんなときのために ~/.emacs や ~/.emacs.d/init.el に以下を入れておきます。

(server-start)
(setenv "EDITOR"
        (concat (getenv "emacs_dir") "/bin/emacsclient.exe"))

スラッシュでなくバックスラッシュ(円記号)を使用するとうまくいきません。

環境変数 emacs_dir は Windows 版の Emacs でしか設定されないようです。

shell-mode 以外に、async-shell-command (M-&) でも git を実行できます。
shell-command (M-!) で実行すると Emacs が止まってしまいます。

編集後は save-buffer (C-x C-s) してから kill-buffer (C-x C-k) です。
キャンセルする場合は save-buffer せず kill-buffer を強行(バッファがモディファイされてるけどエニウェイキルるの? に yes)してください。

python で Windows の My Pictures フォルダのパスを取得(OneDrive対応)

python でピクチャフォルダのパスを取得したくて調べました。

OneDrive を使用しているとリダイレクトされてるので、環境変数から組み立てるのではだめでした。

ついでに Local AppData のパスも取得してます。

import ctypes.wintypes

CSIDL_LOCAL_APPDATA = 0x1c # SDK の ShlObj_core.h で定義されてる
CSIDL_MYPICTURES = 0x27

buf = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH)

ctypes.windll.shell32.SHGetFolderPathW(0, CSIDL_LOCAL_APPDATA, 0, 0, buf)
print('LOCAL_APPDATA={}'.format(buf.value))

ctypes.windll.shell32.SHGetFolderPathW(0, CSIDL_MYPICTURES, 0, 0, buf)
print('MYPICTURES={}'.format(buf.value))

出力

LOCAL_APPDATA=C:\Users\perce-neige\AppData\Local
MYPICTURES=C:\Users\perce-neige\OneDrive\画像

改元対応の記録

私の作ってる Windows アプリケーションの改元対応の記録です。

  • 2018年8月 改元対応にともなうバグが発覚して修正した。
  • 2019年4月1日(改元1月前) 次の元号は「令和」であることが発表される。
  • 2019年4月12日 Windows Update を待っていると令和対応版の出荷ができないので、元号選択肢の取得はレジストリのデータに、無ければ「令和」も加え、日付の和暦への変換は独自実装に切り替える。ついでに「元年」にも対応する。
  • 2019年4月16日 令和対応版リリース

結局のところ Windows API 関数ってのはその時点の Windows にアクセスするものであって、ライブラリ関数ではないということなのでしょう。日付の和暦への変換などは Windows に頼らずに、アプリケーション独自で実装するか、別のライブラリを利用すべきということがわかりました。

追記
  • 2019年4月26日(改元5日前; 米国25日) Windows 10 version 1803 までの Update が発行された。Windows 8.1 以前は preview なので、普通に適用されるのは 5月15日になる模様。Windows 10 version 1809 用は "Coming soon!" (日本語サイトでは「近日公開予定」)となっている。