2025/10/18日での、MSVC STL cmathのpowの話。
2乗(整数指数時)のpow関数はx*xを返すようになった(戻した)らしい。
詳しくはここを参考。
簡単にはstd::powが呼び出していたUCRTのpowのせいらしい。
It turns out that the UCRT mishandles squaring in a small fraction of cases, returning answers that are off by 1 ULP (unit in the last place).
下記はMSVC STLの実装。
// https://github.com/microsoft/STL/blob/57c30cabbaa6bb77a59a2acee21e2c6ed7b22322/stl/inc/cmath template <class _Ty1, class _Ty2, _STD enable_if_t<_STD is_arithmetic_v<_Ty1> && _STD is_arithmetic_v<_Ty2>, int> = 0> _NODISCARD _STD _Common_float_type_t<_Ty1, _Ty2> pow(_Ty1 _Left, _Ty2 _Right) noexcept /* strengthened */ { if constexpr (_STD _Is_nonbool_integral<_Ty2>) { if (_Right == 2) { return static_cast<double>(_Left) * static_cast<double>(_Left); // TRANSITION, see GH-5768 } } return _CSTD pow(static_cast<double>(_Left), static_cast<double>(_Right)); }
整数指数のときのみの分岐なので、pow(x, 2.0) などは依然としてUCRTの精度問題の影響を受ける模様。 実際、pow(x, 2) とpow(x, 2.0) を比較する簡単なテストをしてみると、
#include <print> #include <cmath> #include <random> int main() { constexpr int n = 10000000; int mismatch = 0; std::mt19937 gen(std::random_device{}()); std::uniform_real_distribution<double> dist(-1e2, 1e2); for (int i = 0; i < n; ++i) { double x = dist(gen); double res1 = std::pow(x, 2); double res2 = std::pow(x, 2.0); if (res1 != res2) { ++mismatch; if (mismatch < 10) { std::print("Mismatch example ({}): x = {:>20.17}, pow(x, 2) = {:>20.17}, pow(x, 2.0) = {:>20.17}\n", mismatch, x, res1, res2 ); } } } std::print("Total mismatches: {} out of {}\n", mismatch, n); return 0; }
Mismatch example (1): x = 37.055681642888231, pow(x, 2) = 1373.123542019084, pow(x, 2.0) = 1373.1235420190837 Mismatch example (2): x = 88.463865152448363, pow(x, 2) = 7825.8554377105684, pow(x, 2.0) = 7825.8554377105675 Mismatch example (3): x = 30.671320123173473, pow(x, 2) = 940.72987809818596, pow(x, 2.0) = 940.72987809818608 Mismatch example (4): x = -77.194875334082354, pow(x, 2) = 5959.0487778445158, pow(x, 2.0) = 5959.0487778445167 Mismatch example (5): x = -22.270302890772342, pow(x, 2) = 495.96639084674297, pow(x, 2.0) = 495.96639084674291 Mismatch example (6): x = 68.469678865561832, pow(x, 2) = 4688.0969239531642, pow(x, 2.0) = 4688.0969239531651 Mismatch example (7): x = 88.304096770406147, pow(x, 2) = 7797.6135064372538, pow(x, 2.0) = 7797.6135064372529 Mismatch example (8): x = 0.83606504944175697, pow(x, 2) = 0.69900476689804758, pow(x, 2.0) = 0.69900476689804747 Mismatch example (9): x = -20.204235227699698, pow(x, 2) = 408.21112113622144, pow(x, 2.0) = 408.21112113622149 Total mismatches: 4174 out of 10000000
そこそこの確率で一致しない。
まぁ、2乗でpow関数なんてそもそも使わないし、あんまり気にならないといえば気にならない、でもちょっと気持ち悪いねって感じ。
そもそもVS2019 16.8以降、powの精度がおかしくなったから修正したという話なので、気持ち悪い状態からちょっと気持ち悪いかも、くらいになったと考えればよいか。
至極どうでもいいことだが、2乗の時に"pow関数"みたいなものをなぜ使わないのか、の説明がしづらい。
「stdのpowは一般的なnに対しては有効だけど、2乗や3乗のようなケースではオーバヘッドが大きいよ(知識マウント)」
「え、2乗のケースはstd::pow(x, 2) って書いてもコンパイラがx*xと同じコード吐き出すでしょ?MSVCの実装はそうやってるし」
みたいな反論の隙を与えてしまう。
MSVCのstd::powを使うときは、指数は整数型よりdoubleにした方が一分岐省略できる、というトリビアで、今後は知識マウントを取りたい所存。
本文とは全然関係ないが、VS 2026の配色テーマ名がやや面白い。
これからは「好きな配色:月の光の輝き」とか自己紹介できるのアツい。
どうせだったらマンゴーパラダイスとかも「芳醇な果実たちの楽園」みたいにしてほしかった。
