Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions 0050-powx-n/0050-powx-n.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
## Step 1

- 問題文
- `pow(x, n)`を実装せよ。 $x^n$ の計算。
- 制約:
- -100.0 < x < 100.0
- -2^31 <= n <= 2^31-1
- n is an integer.
- Either x is not zero or n > 0.
- -10^4 <= x^n <= 10^4

### 実装1

- アルゴリズムの選択
- nが非常に大きいため、掛け算をn回繰り返すと時間がかかる。
- 一方、x^nのレンジは比較的狭く、nが大きい場合底xは比較的小さめと思われる。
- x^10 = x^(8 + 2) = x^8 * x^2 のような分解を考える。
- 指数法則から、x^8 = (x^4)^2 = ((x^2)^2)^2 となり、x^2を2回自乗してたどり着ける。
- この方法を使えばO(log n)で計算可能。
- 実装
- loopで書く。[log n]回まわる。
- 分解にはabs(n)に対するbit操作が使えそう。
- 計算量
- Time: O(n)
- Space: O(1)

```python3
class Solution:
def myPow(self, x: float, n: int) -> float:
abs_n = abs(n)
result = 1
power = x
for shift in range(abs_n.bit_length()):
if abs_n >> shift & 1:
result *= power
power *= power

if n < 0:
return 1 / result
return result
```

- ここまで20分。
- `power *= power`を当初`power **= 2`と書いていたところ、OverflowError: (34, 'Numerical result out of range')となり、困っていた。
- (そもそも、x ** nはよく考えるとpow(x, n)なので禁止技だった。
- GPT-5に聞くと;
> - `power *= power` は浮動小数点の**乗算**です。IEEE 754 に従い、オーバーフローしても例外を出さずに `inf`(無限大)になります。
> - `power **= 2` は内部的に浮動小数点の **べき演算**(libm の `pow`)を通ります。こちらはオーバーフロー時に `errno=ERANGE` を立て、Python では `OverflowError: (34, 'Numerical result out of range')` を送出します。
>
> 今回、反復自乗で `power` が `DBL_MAX≈1.797e308` を超えたタイミングで、`*=` だと黙って `inf` に落ち着くのに対し、`**=` だと例外が飛びます。これが差です。
- ほえー。`power **= 2`の方でもtry-exceptでOverflowErrorをmath.infに置き換えると動いた。
- 補足:
- n == 0のとき、forループがskipされてreturn 1となる。
- (0).bit_length()は0。
- x == 0のとき、forループは[log n]回まわるがずっとpower == 0であり、resultも0で上書きされる。
- これはエッジケース高速化のために入力時点でreturn 0しても良いと思った。
- n == 0 and x == 0のとき、forループがskipされてreturn 1となる。
- 入力制約上は対応する必要はないが、1が返ったらいいと思っていたらたまたまなったのでよかった。
- pythonのpow(0, 0)も1が返った。

## Step 2

- [コメント集](https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.d9ky6ipkmw98)
- https://discordapp.com/channels/1084280443945353267/1262688866326941718/1351742235854639154
- > IEEE-754の内部ビットの数も覚えておくと、面接でよく分かっている風が醸せることがあります。exponent が8ビットと11ビットです。符号が1ビットで残りが23ビットと52ビットです。
- https://github.com/hroc135/leetcode/pull/43
- https://ja.wikipedia.org/wiki/IEEE_754
- 浮動小数点の処理は言語間で合意がある。
- LLMだと表現力を効率よく上げるためにパラメータを量子化していたりする。
- https://discord.com/channels/1084280443945353267/1231966485610758196/1359997459232981234
- > 私の感覚はこういう方が素直です。n x を破壊していないし、base bit の関係がはっきりしているからですね。
- bit = 2^shiftで考え、ループ条件をbit <= nと書く方法。
- 自分はshift == n.bit_length()をループ終了条件と見る方が(わかりやすくて)好みかもしれない。
- https://github.com/nanae772/leetcode-arai60/pull/44
- https://discordapp.com/channels/1084280443945353267/1410553251409039410/1429045053627568138
- `1.797e308`はfloat (64bit) で表現できる値の最大値。
- sys.float_info.maxで閲覧できる。
- 64bit浮動小数点はexponentが11bitで、biasも考慮してexponentの値域は[−1022, +1023]。よって最大値は概ね2^1024。
- (2 − 2^−52) × 2^1023 = (1 − 2^−53) × 2^1024 ≈ 2^1024 ≈ 1.797...e+308

## Step 3

### 実装2

- bit = 2^shiftで考え、ループ条件をbit <= nと書く方法。

```python3
class Solution:
def myPow(self, x: float, n: int) -> float:
if n == 0:
return 1
if x == 0:
return 0

result = 1
bit = 1
power = x
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

xを何乗かしたものであるというのを明らかにしておくために、x_poweredみたいな名前にしてもいいと思いました。

abs_n = abs(n)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

absで回さずに、if n < 0: return 1 / myPow(x, -n)を最初の方に持ってきてここにくる段階ではn > 0の時を考えれば良いようにした方が見やすくなる気がしました。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。

確かにそうですね。その手があったんですね。

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

関数を一段階深くするのもいいんですが、ちょっと重い(といっても数ナノ秒、どちらかというとスタックが深くなるのでデバッグが面倒になるほうが気にしているかもな)ので条件分岐で済むならそちらも一つでしょう。

while bit <= abs_n:
if abs_n & bit:
result *= power
power *= power
bit <<= 1

if n < 0:
return 1 / result
return result
```