微分と積分が逆関係であることの直観的図解

# 「微分積分学の基本定理」を肌感覚で理解する

微分と積分を初めて学んだ方の多くが抱くのは、「なぜ積分すると面積が求まるのか」という疑問ではないでしょうか。この疑問は結局はっきり解消されないまま過ごす方も多いようで、学生か否かに関わらず時折話題になることもあります。

この疑問がなかなか解決されない根本的な理由は、やはり"完全に"理解するためにはどうしても厳密性が必要であるからでしょう。「微分積分学の基本定理」と大層な名が付いているとおり、数学の歴史の中でも難問かつそれゆえに重要な発見であったことは確かです。

しかしながら、完璧な厳密性を求めなければ、非常にわかりやすく、それでいて本質のほとんどを捉えた直観的な説明が可能であると私は考えています。早速その方法を紹介しましょう。


## 図解

全ての説明は以下の図に集約されます。左を微分すると右に変化し、右を積分すると左になります。

<!-- ![Figure_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551504/09a0c894-78ab-5906-7f5c-7bc183497d26.png)
-->

![Figure_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551504/43c10434-5549-72ef-2d08-6bc4daecdb8c.png)




## 簡単な解説
まずはグラフのスケールなど怪しい所は気にせず、大雑把なイメージから解説します。微分とは本質的に「**差分**」です。左の階段状に増えていくグラフの差分は、右のように各階段の上がり幅を順に記録したものとなります。

この右のグラフの面積を求めたいとしましょう。全体の面積は各短冊の合計ですが、これらを縦に細長く並べ替えても面積は変わりません。その「縦に細長く並べた」図形というのは、まさしく左のグラフの$x=0.9〜1$に全く同じものが現れています！原始関数の値$F(1)$が、導関数の$x=0〜1$における面積と等しいこととちょうど対応します。


## 細かい解説

ここまで読んで、「面積と高さは別物では」や「そもそもグラフのスケールが違う」などの疑問が生じたかもしれません。それらについて解説していきます。

まず補足ですが、右のグラフの高さは順に $0.1,0.2,\ldots,1$ です。また左のグラフの高さは $0.01,0.03,0.06,\ldots,0.55$ のように、等差数列になります。

### グラフのスケール
微分はもう少し正確に言えば、$(yの差分)/(xの差分)$ のことです。右のグラフには各階段の高さそのものではなく、それを階段の幅$\Delta x$で割ったものが記録されています。この場合は$\Delta x = 0.1$なので、$0.01$や$0.02$ではなく、それを10倍した$0.1$や$0.2$が記録されているわけです。

ここで、このままだと右のグラフは各短冊が10倍の高さに見えてしまいます。そこで見やすさおよび考えやすさのために、右のグラフy軸の縮尺を1/10倍（=$\Delta x$倍）にして、見かけ上の高さを揃えているのです。これが、左右のグラフのスケールを敢えて異ならせている理由です。

### 面積と高さの整合性

当然ですが、四角形の面積と高さは一般に一致しません。ならばなぜ、今回の事例では面積の等しいことと原始関数値の等しいことが関連するのでしょう。

ゴールから逆算してみましょう。一体どういうときに面積と高さが一致するのか。それは底辺の長さが$1$のときです。四角形の底辺が1ならば、面積と高さが一致してとても考えやすくなります。このように「底辺が単位長さであると理想化して考える」ことこそが、面積と高さを同一視できるカラクリです。

話を戻します。面積が原始関数に等しいことを示す理想の論法としては、「*右のグラフの面積と左のグラフの縦長短冊（底辺1）の面積が等しい。したがって、右の面積と左の高さが等しい。*」といったところです。

この理想からのズレを考えてみます。実際の左のグラフでは、縦長短冊の幅は$\Delta x$$(=0.1)$です。さらに縮尺の関係上、右を基準とすると、左のグラフの高さは本来より10倍（=$\frac{1}{\Delta x}$倍）大きい見た目で描かれていることが分かります。

総合すると、現状の左の縦長短冊は**理想よりも横幅が$\Delta x$倍、縦幅が$\frac{1}{\Delta x}$倍されて表示されている**わけです。ここで気付きます！現状においてもなお、横と縦の倍率が相殺されることで、面積としては理想のものと変わっていないのです。ゆえに、「右の面積 = 現状の縦長短冊の面積 = 理想の縦長短冊の面積 = 理想の縦長短冊の高さ」と演繹されるのですが、理想の縦長短冊の高さとは、右のスケールに合わせた本来の高さのことであり、これは原始関数の値そのものに他なりません（ちょっと難解かも）。

かくして、面積と積分が等しいことを確認できました。

<details><summary>※別解</summary>

上記の解説は感覚に訴えすぎて、むしろ分かりにくかったかもしれません。
$$h_1=0.01, h_2=0.02, \ldots, h_{10}=0.1$$とすると、右側の短冊の高さは各$i$に対して$\frac{h_i}{\Delta x}$です。求めたい面積は
$$S = \sum_{i=1}^{10} \frac{h_i}{\Delta x} \cdot \Delta x = \sum_{i=1}^{10} h_i$$であり、左側グラフの高さと一致することが分かります。

次元の観点でも、微分は一度$x$で割られており、それの面積を求める（$x$を掛ける）と元に戻ると説明できます。

</details>


### 刻み幅を変えてみる

上記の議論は刻み幅$\Delta x$を$0.1$以外に変えた場合でも成り立ちます。例えば$x$の範囲を保ったまま$\Delta x$をさらに小さくした場合、左側グラフのスケールはさらに拡大され、同時により急峻な見た目になります[^1]が、諸々の計算結果は変わりません。

[^1]: ソースコードの`segments`を増やして実行すると、実際に確認できます。

特に$\Delta x=1$とすれば、上述の「理想の縦長短冊」を直接得られますし、グラフのスケールも一致します。直観的な説明としてはこの時こそ分かりやすくなるはずです。

ただ、微分積分の本質の一つは「$\Delta x$を$0$に飛ばした極限の世界で考える」ことです。$\Delta x$が実数の限りにおいてどんな値であっても"面積=微分の逆"であることを示すことが、その理解の一助になります。しかし、$\Delta x$があらゆる実数のときに成り立つからといって、極限に飛ばしたときにも各性質が成り立つかは非自明です。この部分は今回の例では示せておらず、さらなる考察、あるいはそれこそ"完璧な厳密性"が必要になってくるでしょう。

（ただ、どんな関数もx軸方向に引き伸ばせばy軸の階段による誤差をいくらでも小さくできそう(?)なので、その方針ならば$\Delta x=1$を保ったまま考えられるかもしれません）



# まとめ

私見ながら、微分積分の本質は大きく以下の2つであると考えています。
- **差分および総和である**
- (刻み幅)→$0$の極限を取る

扱いの厄介な極限操作の結果である「微分」「積分」へ話を進める前に、その前段階である差分と総和の枠組みで説明できれば直観的です。

今回の記事ではこの考えに基づき、$\Delta x$を特定の値に固定してあるならば、（離散版の）導関数が描く面積と元の関数値が一致することを視覚的に示しました。本主張の肝はとにかく上記の図示に尽きます。細かいことは気にせず、差分として図にすれば必然的に総和が現れる、ということを伝えたいのです。

本記事は王道の説明・証明方法とは少し異なり、厳密性を欠いてでも平易に体感できることに重点を置きました。そして、こうした身に沁みた理解こそ、厳密な証明にステップアップするためにも重要ではないかと考えています。

私的な思いつきを記事にしてみた形ではありますが、特に「なぜ積分すると面積が求まるのか」という文脈において、原始関数を短冊の累積としてズバリ図解したような先例はあまり見かけないように感じます[^2]。このスタイルの説明がさらに洗練されていくことを望んでいます。

[^2]: 先行例: <https://bibunsekibun.wordpress.com/2015/03/28/なぜ積分すると面積が求まるのか/>



# （付録）ソースコード

技術記事としての体裁を守るために、ソースコードを掲載します。

```python:biseki.py
# SPDX-License-Identifier: Unlicense

import matplotlib.pyplot as plt
from matplotlib import patches
from matplotlib.ticker import FuncFormatter
import numpy as np

plt.rcParams["mathtext.fontset"] = "cm"
plt.rcParams["font.size"] = 14
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(9, 9))
ax1.set_title(r"$F\,(x)$")
ax2.set_title(r"$f\,(x)$")


def get_color(i):
    return plt.get_cmap("rainbow")(30 + i * 19)


l = 0
r = 1
segments = 10  # 増やしすぎると負荷
dx = (r - l) / segments


# 導関数
def f(x):
    # return np.sin(x * 3 / (r - l)) / 3 + 0.1  # おまけ：別の関数形
    return x + dx


x = np.linspace(l, r, segments, endpoint=False)
y = f(x)

xticks = np.linspace(l, r, 3)
ymax = 6

ax1.set_xticks(xticks)
ax1.set_ylim(0, ymax * dx)
ax1.set_axisbelow(True)
ax1.grid()
ax2.set_xticks(xticks)
ax2.set_ylim(0, ymax)
ax2.set_axisbelow(True)
ax2.grid()


# 軸ラベルのフォーマットを設定。必要なときだけ小数点を表示
def format_func(value, tick_number):
    if int(value) == value:
        return int(value)
    else:
        return value


ax1.xaxis.set_major_formatter(FuncFormatter(format_func))
ax2.xaxis.set_major_formatter(FuncFormatter(format_func))


# 棒グラフ描画
for i in range(segments):
    r = patches.Rectangle(
        xy=(x[i], 0),
        width=dx,
        height=y[i],
        facecolor=get_color(i),
        edgecolor="gray",
    )
    ax2.add_patch(r)

for i in range(segments):
    accum = 0
    for j in range(i + 1):
        r = patches.Rectangle(
            xy=(x[i], accum),
            width=dx,
            height=y[j] * dx,
            facecolor=get_color(j),
            edgecolor="gray",
        )
        accum += y[j] * dx
        ax1.add_patch(r)

plt.show()
```