あなたの 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>

VC, CMAKE でアセンブラ リスティング ファイル出力

VC++ で書いたコードをチューニングしていて、単純なコードなのに遅いってことありますよね。
で、アセンブラ コードを見てみたくなったときに使用するのがこのコンパイル オプションです。

/FA、/Fa (リスティング ファイル) | Microsoft Learn

マシンコード込み, ソースコード込みにするため、/FAcs を使用するのがいいと思います。
生成されるファイル名は、ソースファイルの拡張子を .cod に変えたものになります。
オブジェクトファイル(.obj) と同じディレクトリに生成されます。

cmake を使用していて、ターゲット(exe とか dll とか)単位で指定すればいい場合は、
CMakeLists.txt で add_executable や add_library よりも下に以下のように書きます。

target_compile_options(target PRIVATE /FAcs)

ソースファイル単位で指定したい場合は以下のように書きます。

set_source_files_properties(source.cpp PROPERTIES COMPILE_FLAGS /FAcs)

ソースファイルは複数並べて書けます。

set_source_files_properties(source1.cpp source2.cpp PROPERTIES COMPILE_FLAGS /FAcs)

ふるさと納税の限度額計算

去年分の確定申告(還付申告)が終わったので、今年のふるさと納税の計画を立てたい。

まずは 総務省 | ふるさと納税のしくみ | 税金の控除について を参照。

ここの③の式と③'の式が連立する金額がふるさと納税で損をしない限度額となるようだ。

すなわち

    (ふるさと納税額 - 2,000円)×(100% - 10%(基本分) - 所得税の税率) = (住民税所得割額)×20%

ここで [ふるさと納税考慮前の住民税課税所得金額] から [ふるさと納税の限度額] を求めたい。

x と × の区別がつきにくいので、乗算は * で表す。

[ふるさと納税の限度額] を x, [ふるさと納税考慮前の住民税課税所得金額] を y, [住民税の税率] を 0.1, [住民税の調整控除] を -2500 として他に控除が無いとすると、

    [住民税所得割額] = (y - (x - 2000)) * 0.1 - 2500

であるのでこれを先の式に代入し

    (x - 2000) * (0.9 - [所得税の税率]) = ((y - x + 2000) * 0.1 - 2500) * 0.2

を得る。

これを x について解くと

    x = (y * 0.02 - 460 + 2000 * (0.9 - [所得税の税率])) / (0.92 - [所得税の税率])

確定申告をするとし、[所得税の税率] は復興税込みで 0.1021 とすると

    x = y * 200/8179 + 11358000/8179

小数(有効3桁)にすると

    x = y * 0.0244 + 1390

すなわち

    [ふるさと納税の限度額] = [ふるさと納税考慮前の住民税課税所得金額] * 0.0244 + 1390

また、 [ふるさと納税考慮前の住民税] を z, [住民税均等割] を市県等合計で 5000 とすると、

    z = y * 0.1 - 2500 + 5000

であるので、これを y について解いた

    y = (z - 2500) * 10

を用いて計算することもできる。

すなわち

    x = z * 2000/8179 + 6358000/8179

小数(有効3桁)にすると

    x = z * 0.244 + 777

すなわち

    [ふるさと納税の限度額] = [ふるさと納税考慮前の住民税] * 0.244 + 777

【注意】 以上の計算は自分専用です。 人によって条件が異なるので鵜呑みにしないようにしてください。 ご相談は税理士にしてください。

2023-03-02 気になったところを修正しました。

 

広告

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 などでググってください。