税をソースコードで考える

# はじめに

毎年11月11日から11月17日までの1週間は[税を考える週間](https://ja.wikipedia.org/wiki/%E7%A8%8E%E3%82%92%E8%80%83%E3%81%88%E3%82%8B%E9%80%B1%E9%96%93)らしいです。
自分なりに税の仕組みを考えようと思います。

ところで、確定申告の計算はとても難しいと感じます。
[税務署の提供情報](https://www.nta.go.jp/taxes/shiraberu/shinkoku/tokushu/yoshiki.htm)を見ても、完全に理解できているか怪しい気持ちになります。
もしかして、計算内容をソースコードで表現すると、少しでも理解しやすくなるのではと思い、試してみました。

# 先に結論を

[確定申告書Aの手引き](https://www.nta.go.jp/taxes/shiraberu/shinkoku/tebiki/2019/pdf/001.pdf)に計算式が記載されているものについて、D言語のソースコードで表現しました。
私がD言語で書いてみてよかったと思う点は、以下の通りです。

* 数値に`_`を区切り文字に使えるので、ソースコードが読みやすい。<br>`1_000_000`は、`1000000`と同じく百万という数値として扱えます。
* `unittest`が言語仕様に含まれているので、面倒な計算ロジックの検証をソースコード内に組み込める。<br>ユニットテストは簡単に何度でも実行できます。
* 関数内関数（関数のネスト）が記述できるので、関数内の同じ処理をまとめてスッキリ。

ただ、ソースコードにしても難しいものは難しい。（残念）
以降は、税金の計算をアルゴリズムと思って楽しんでもらえると幸いです。

# 確定申告の書式
確定申告書は、2種類用意されています。
この記事では、申告書Aが対象です。

|申告書の種類|対象者|
|:-----------|:-----------|
|申告書A|申告する所得が以下のみの方。<br>給与所得や公的年金等・その他の雑所得、配当所得、一時所得。|
|申告書B|所得の種類にかかわらず、どなたも使用できます。|
※前年分から繰り越された損失額を本年分から差し引く場合は、申告書Bを使用します。

<details><summary>申告書Aの様式</summary>
![sheeta.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/0a5530eb-e7c8-864a-b86f-99bba9cbe4c8.png)</details>

# 手引書の手順２　所得金額を計算する

[確定申告書Aの手引き](https://www.nta.go.jp/taxes/shiraberu/shinkoku/tebiki/2019/pdf/001.pdf)の手引きを参考にソースコードを書いていきます。
手引きに計算欄がある項目が対象になります。

## ㋐から①への計算

もらった給料すべてが課税対象とはなりません。
まずは、もらった給料から課税対象となる給与所得を求めます。
![a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/8e67e903-10bb-d84e-eb4c-9ea7462647b3.png)
計算欄が、いきなり複雑です・・・:confused:
計算欄のA、B、Cとソースコードの変数 a, b, c は対応させました。
計算のインプットとして必要な金額は関数のパラメータ、インプットから算出できるものはローカル変数としています。

```d:
import std;

long get_aTo01(long a)
{
	long c;
	if        ( a <    651_000 ){
		c = 0;
	} else if ( a <  1_619_000 ){
		c = a - 650_000;
	} else if ( a <  1_628_000 ){
		long[] boundary = [1_620_000, 1_622_000, 1_624_000];
		long[] amount   = [969_000, 970_000, 972_000, 974_000];
		auto index = count!("a <= b")(boundary, a);
		c = amount[index];
	} else if ( a <  6_600_000 ){
		long[] boundary  = [1_800_000, 3_600_000];
		long[] rate      = [24, 28, 32];
		long[] deduction = [0, 180_000, 540_000];
		long b = a / 4000 * 1000;	// 千円未満の端数切捨て
		auto index = count!("a <= b")(boundary, a);
		c = b * rate[index] / 10 - deduction[index];
	} else if ( a < 10_600_000 ){
		c = a * 9 / 10 - 1_200_000;
	} else {
		c = a - 2_200_000;
	}
	return ( c );
}

unittest {
	assert(get_aTo01(        -1) ==         0);
	assert(get_aTo01(   650_999) ==         0);
	assert(get_aTo01(   651_000) ==     1_000);
	assert(get_aTo01( 1_618_999) ==   968_999);
	assert(get_aTo01( 1_619_000) ==   969_000);
	assert(get_aTo01( 1_619_999) ==   969_000);
	assert(get_aTo01( 1_620_000) ==   970_000);
	assert(get_aTo01( 1_621_999) ==   970_000);
	assert(get_aTo01( 1_622_000) ==   972_000);
	assert(get_aTo01( 1_623_999) ==   972_000);
	assert(get_aTo01( 1_624_000) ==   974_000);
	assert(get_aTo01( 1_627_999) ==   974_000);
	assert(get_aTo01( 1_628_000) ==   976_800);
	assert(get_aTo01( 1_800_000) == 1_080_000);
	assert(get_aTo01( 3_600_000) == 2_340_000);
	assert(get_aTo01( 6_600_000) == 4_740_000);
	assert(get_aTo01(10_000_000) == 7_800_000);
}
```

[count関数](https://dlang.org/phobos/std_algorithm_searching.html#.count)を使うことで`if`分岐をなるべく減らしてみました。

```d:抜粋
    long[] boundary = [1_620_000, 1_622_000, 1_624_000];
    long[] amount   = [969_000, 970_000, 972_000, 974_000];
    auto index = count!("a <= b")(boundary, a);
    c = amount[index];
```
`count`関数では、`boundary`を閾値で使っていて、変数`a`が閾値をいくつ超えるかを判定しています。
その値を`index`にセットしています。
`index`の値に従って、配列`amount`より、給与所得を取得します。

D言語の言語仕様として、`unittest`をソースコードに書くことができます。
`unittest`の実行例は、以下の通りです。テストの結果、問題なし(`pass`)です。

```text:テスト実行例
D:\Dev> dmd -m64 -main -unittest calctax.d

D:\Dev> calctax
1 modules passed unittests
```

## ㋑㋒から②への計算

あらためて読み返すと、年金や印税収入は雑所得扱いなのですね。
![i.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/2a01d016-96b3-0e76-e368-0da0e3278bdd.png)

```d:
import std;

long get_iuTo02(int yyyymmdd, long a, long c, long d)
{
	long[] boundary  = [700_001, 1_300_000, 4_100_000, 7_700_000];
	long[] deduction = [0, 700_000, 375_000, 785_000, 1_555_000];
	if ( yyyymmdd <= 19550101 ){	// 昭和30年1月1日以前に生まれた方
		boundary[0]  = 1_200_001;
		deduction[1] = 1_200_000;
	}
	double[] rate   = [0.0, 1.0, 0.75, 0.85, 0.95];
	auto index = count!("a <= b")(boundary, a);
	long b = cast(long)(a * rate[index]) - deduction[index];
	
	long e = c - d;
	long f = b + e;
	return ( f );
}

unittest {
	assert(get_iuTo02(19550102,         0, 0, 0) ==         0);
	assert(get_iuTo02(19550102,   700_000, 0, 0) ==         0);
	assert(get_iuTo02(19550102,   700_001, 0, 0) ==         1);
	assert(get_iuTo02(19550102, 1_299_999, 0, 0) ==   599_999);
	assert(get_iuTo02(19550102, 1_300_000, 0, 0) ==   600_000);
	assert(get_iuTo02(19550102, 4_099_999, 0, 0) == 2_699_999);
	assert(get_iuTo02(19550102, 4_100_000, 0, 0) == 2_700_000);
	assert(get_iuTo02(19550102, 7_699_999, 0, 0) == 5_759_999);
	assert(get_iuTo02(19550102, 7_700_000, 0, 0) == 5_760_000);
	
	assert(get_iuTo02(19550101,         0, 0, 0) ==         0);
	assert(get_iuTo02(19550101, 1_200_000, 0, 0) ==         0);
	assert(get_iuTo02(19550101, 1_200_001, 0, 0) ==         1);
	assert(get_iuTo02(19550101, 3_299_999, 0, 0) == 2_099_999);
	assert(get_iuTo02(19550101, 3_300_000, 0, 0) == 2_100_000);
	
	assert(get_iuTo02(19550101, 0, 100_000,      0) == 100_000);
	assert(get_iuTo02(19550101, 0, 100_000, 20_000) ==  80_000);
}
```

## ㋓から③への計算

配当収入は総合課税、分離課税を選択可能です。
ソースコードを書くまでもなさそうですが、ついでに。
![e.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/c25f09e4-a0d3-41d5-0453-60562948f0e1.png)

```d:
import std;

long get_eTo03(long a, long b)
{
	long c = max(a - b, 0);
	return ( c );
}

unittest {
	assert(get_eTo03(        0,       0) ==         0);
	assert(get_eTo03(        0, 100_000) ==         0);
	assert(get_eTo03(1_000_000,       0) == 1_000_000);
	assert(get_eTo03(1_000_000, 100_000) ==   900_000);
}
```

## ㋔から④への計算

50万円以上の当せんや競馬の払い戻しなどがあった場合は一時所得になります。
![o.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/1227415d-5241-9602-99e1-7c0154914625.png)

```d:
import std;

long get_oTo04(long a, long b)
{
	long c = max(a - b, 0);
	long d = min(c, 500_000);
	long e = c - d;
	long f = e / 2;
	return ( f );
}

unittest {
	assert(get_oTo04(      0,       0) ==      0);
	assert(get_oTo04(500_000,       0) ==      0);
	assert(get_oTo04(500_002,       0) ==      1);
	assert(get_oTo04(600_000, 600_001) ==      0);
	assert(get_oTo04(700_000, 100_000) == 50_000);
}
```

# 手引書の手順３　所得から差し引かれる金額（所得控除）を計算する

手引きに計算欄がある項目が対象にソースコードを書いていきます。

## 生命保険料控除
所得は1円未満切り捨てでしたが、控除では1円未満切り上げなのを注意しつつ、書きます。
しかし、なんでこんなに複雑な計算式なのでしょうか。
![8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/5872782e-324f-0018-5233-951a51919535.png)

D言語は関数内に関数を書くことができます。（関数のネスト）
同じような処理は、関数内関数`oldDeduction`、`newDeduction`で処理しました。

```d:
import std;

long get08(long a, long b, long e, long f, long g)
{
	long oldDeduction(long x){
		if        ( x < 25_000 ){
			return ( x );
		} else if ( x < 50_000 ){
			return ( (x + 1) / 2 + 12_500 );			// 1円未満切り上げ
		}
		return ( min((x + 3) / 4 + 25_000, 50_000) );	// 1円未満切り上げ
	}
	
	long newDeduction(long x){
		if        ( x < 20_000 ){
			return ( x );
		} else if ( x < 40_000 ){
			return ( (x + 1) / 2 + 10_000 );			// 1円未満切り上げ
		}
		return ( min((x + 3) / 4 + 20_000, 40_000) );	// 1円未満切り上げ
	}
	
	long c = oldDeduction(a);
	long d = oldDeduction(b);
	long h = newDeduction(e);
	long i = newDeduction(f);
	long j = newDeduction(g);
	long k = max(min(c, 50_000), min(c + h, 40_000));
	long l = max(min(d, 50_000), min(d + i, 40_000));
	long m = min(j, 40_000);
	long n = min(k + l + m, 120_000);
	return ( n );
}

unittest {
	assert(get08(      0, 0, 0, 0, 0) ==      0);
	assert(get08( 24_999, 0, 0, 0, 0) == 24_999);
	assert(get08( 25_000, 0, 0, 0, 0) == 25_000);
	assert(get08( 49_999, 0, 0, 0, 0) == 37_500);
	assert(get08( 50_000, 0, 0, 0, 0) == 37_500);
	assert(get08(100_001, 0, 0, 0, 0) == 50_000);
	assert(get08(0,  24_999, 0, 0, 0) == 24_999);
	assert(get08(0,  25_000, 0, 0, 0) == 25_000);
	assert(get08(0,  49_999, 0, 0, 0) == 37_500);
	assert(get08(0,  50_000, 0, 0, 0) == 37_500);
	assert(get08(0, 100_001, 0, 0, 0) == 50_000);
	assert(get08(0, 0,  20_000, 0, 0) == 20_000);
	assert(get08(0, 0,  20_001, 0, 0) == 20_001);
	assert(get08(0, 0,  40_000, 0, 0) == 30_000);
	assert(get08(0, 0,  40_001, 0, 0) == 30_001);
	assert(get08(0, 0,  80_001, 0, 0) == 40_000);
	assert(get08(0, 0, 0,  20_000, 0) == 20_000);
	assert(get08(0, 0, 0,  20_001, 0) == 20_001);
	assert(get08(0, 0, 0,  40_000, 0) == 30_000);
	assert(get08(0, 0, 0,  40_001, 0) == 30_001);
	assert(get08(0, 0, 0,  80_001, 0) == 40_000);
	assert(get08(0, 0, 0, 0,  20_000) == 20_000);
	assert(get08(0, 0, 0, 0,  20_001) == 20_001);
	assert(get08(0, 0, 0, 0,  40_000) == 30_000);
	assert(get08(0, 0, 0, 0,  40_001) == 30_001);
	assert(get08(0, 0, 0, 0,  80_001) == 40_000);
	assert(get08( 50_000, 0, 80_001, 0, 0) == 40_000);
	assert(get08(100_001, 0, 80_001, 0, 0) == 50_000);
	assert(get08(0,  50_000, 0, 80_001, 0) == 40_000);
	assert(get08(0, 100_001, 0, 80_001, 0) == 50_000);
	assert(get08(100_001, 100_001, 0, 0, 80_001) == 120_000);
}
```

## 地震保険料控除

無心にソースコードを書きます。変数`h`や`j`が何を意味するか全く不明です。:expressionless:
![9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/95841998-f983-aa54-1501-fa06a71eb996.png)

```d:
import std;

long get09(long a, long b, long c, long d)
{
	long getDeduction(long x){
		if ( x < 10_000 ){
			return ( x );
		}
		return ( min((x + 1) / 2 + 5_000, 15_000) );	// 1円未満切り上げ
	}
	
	long e = a + b;
	long f = c + d;
	long g = getDeduction(d);
	long h = min(e + g, 50_000);
	long i = getDeduction(f);
	long j = min(a + i, 50_000);
	long k = max(h, j);
	return ( k );
}

unittest {
	assert(get09(     0, 0, 0, 0) ==      0);
	assert(get09(50_000, 0, 0, 0) == 50_000);
	assert(get09(50_001, 0, 0, 0) == 50_000);
	assert(get09(0, 50_000, 0, 0) == 50_000);
	assert(get09(0, 50_001, 0, 0) == 50_000);
	assert(get09(0, 0, 0, 10_000) == 10_000);
	assert(get09(0, 0, 0, 10_001) == 10_001);
	assert(get09(0, 0, 0, 20_001) == 15_000);
	assert(get09(0, 0, 5_000, 5_000) == 10_000);
	assert(get09(0, 0, 5_000, 5_001) == 10_001);
	assert(get09(0, 0, 5_000,15_001) == 15_000);
	assert(get09(10_000, 10_000, 5_000,15_000) == 32_500);
	assert(get09(10_000,  2_000, 5_000,15_000) == 25_000);
}
```

## 雑損控除
災害や盗難で損害が発生したときは、所得から控除できます。
![17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/85b8b7b5-c7f3-9218-6794-f478e6332c2d.png)

```d:
import std;

long get17(long a, long b, long d, long g)
{
	long c = max(a - b, 0);
	long e = max(d / 10, 0);
	long f = max(c - e, 0);
	long h = max(g - 50_000, 0);
	long i = max(f, h);
	return ( i );
}

unittest {
	assert(get17(      0,       0,       0,       0) ==       0);
	assert(get17(      0, 100_000,       0,       0) ==       0);
	assert(get17(500_000,       0,       0, 300_000) == 500_000);
	assert(get17(500_000, 100_000,       0, 300_000) == 400_000);
	assert(get17(500_000,       0, 100_001, 300_000) == 490_000);
	assert(get17(500_000, 400_000,       0, 300_000) == 250_000);
}
```

## 医療費控除

医療費控除はこれ以外に、「セルフメディケーション税制による医療費控除の特例」を選択可能とのことですが、省略しています。

![18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/4ed9bea8-42dc-87f6-1ea0-42462669ede3.png)

```d:
import std;

long get18(long a, long b, long d)
{
	long c = max(a - b, 0);
	long e = max(d / 20, 0);
	long f = min(e, 100_000);
	long g = min(max(c - f, 0), 2_000_000);
	return ( g );
}

unittest {
	assert(get18(        0,       0, 2_000_000) ==         0);
	assert(get18(        0, 100_000, 2_000_000) ==         0);
	assert(get18(  100_000,       0, 2_000_000) ==         0);
	assert(get18(  100_001,       0, 2_000_000) ==         1);
	assert(get18(  200_001, 100_000, 2_000_000) ==         1);
	assert(get18(  200_001, 100_000, 1_000_001) ==    50_001);
	assert(get18(2_100_000,       0, 2_000_000) == 2_000_000);
	assert(get18(2_100_001,       0, 2_000_000) == 2_000_000);
}
```

## 寄付金控除

ふるさと納税をしたときなどに関係する寄付金控除です。
![19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/338c962a-ec19-c393-a418-b97b3591190c.png)

```d:
import std;

long get19(long a, long b)
{
	long c = max(b * 4 / 10, 0);
	long d = min(a, c);
	long e = max(d - 2_000, 0);
	return ( e );
}

unittest {
	assert(get19(      0,       0) ==      0);
	assert(get19(100_000, 100_000) == 38_000);
	assert(get19(100_000, 300_000) == 98_000);
}
```

# 手引書の手順４　税金の計算をする

## 所得金額に対する税額
やっとここまで来ました。
手順２、３を使って計算した所得金額から、税額を計算します。
![2122.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/ae8547e0-7316-bed0-3744-0b9f2d40be2c.png)

```d:
import std;

long get21to22(long a, long b)
{
	long c = max(a - b, 0) / 1000 * 1000;	// 千円未満の端数金額を切り捨て
	long[] boundary  = [1_950_000, 3_300_000, 6_950_000, 9_000_000, 18_000_000, 40_000_000];
	long[] taxrate   = [5, 10, 20, 23, 33, 40, 45];
	long[] deduction = [0, 97_500, 427_500, 636_000, 1_536_000, 2_796_000, 4_796_000];
	auto index = count!("a < b")(boundary, c);
	long d = c / 100 * taxrate[index] - deduction[index];
	return ( d );
}

unittest
{
	assert(get21to22(         0, 0) ==          0);
	assert(get21to22(         0, 1) ==          0);
	assert(get21to22( 1_950_000, 0) ==     97_500);
	assert(get21to22( 3_300_000, 0) ==    232_500);
	assert(get21to22( 6_950_000, 0) ==    962_500);
	assert(get21to22( 9_000_000, 0) ==  1_434_000);
	assert(get21to22(18_000_000, 0) ==  4_404_000);
	assert(get21to22(40_000_000, 0) == 13_204_000);
}
```

## 配当控除
配当所得を総合課税で申告した場合、配当控除を受けることができます。
![23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/26592b21-052d-9c13-5da6-ec6a6e9402a5.png)

```d:
import std;

long get23(long a, long b)
{
	b = b / 1000 * 1000;	// 千円未満の端数金額を切り捨て
	long c = max(b - 10_000_000, 0);
	long d = max(a - c, 0);
	long e = d / 10;
	long f = (a - d) / 20;
	long g = e + f;
	return ( g );
}

unittest
{
	assert(get23(    0,          0) ==  0);
	assert(get23(   10,          0) ==  1);
	assert(get23(   10, 10_000_000) ==  1);
	assert(get23(1_010, 10_001_000) == 51);
}
```

# 参考情報

[確定申告書などの様式・手引き](https://www.nta.go.jp/taxes/shiraberu/shinkoku/tokushu/yoshiki.htm)
[確定申告書A](https://www.nta.go.jp/taxes/shiraberu/shinkoku/yoshiki/01/shinkokusho/pdf/r01/01.pdf)
[確定申告書Aの手引き](https://www.nta.go.jp/taxes/shiraberu/shinkoku/tebiki/2019/pdf/001.pdf)