これは、18〜24-bit の信号処理システム用に設計した、最小誤差丸め の 64-bit 平方根(square root)アルゴリズムと C での実装例です。
64-bit の整数の平方根は 32-bit になるので、係数の乗算や右シフトでのオーバーフローやアンダーフローを防ぐため、
MSB の 8-bit 下位付近で演算を行っています。
0x8000 未満の数は 16-bit 上位にスケーリングしてから計算しています。
初期値を 1次の Min-Max 的近似で求め、 次いでニュートン法を3回施し、1 LSB 以内の誤差が得られた後に、LSB を調整し、誤差を ±1/2 LSB に収めています。
本アルゴリズムは 式 (1) と (2) を中核とする。
区間 x ∈ (0.25, 1) における平方根の初期近似として、
定数の除算が 2 の冪になるように調整した 1 次の近似式 (1) を用いる。
この初期近似で、± 0.31 % 以内の精度が得られる。
誤差の低減には、(2) 式のニュートン法を用いている。
重解を持たない場合、ニュートン法では解の近傍では 2 次の収束 を示す。
繰り返しごとの理論精度は次のようになる。
1st: 4.8×10-4
2nd: 1.2×10-7
3rd: 6.5×10-15
ところで、(4) 式を 2 次まで用いると、Halley 法 と等価であるし、3 次まで用いると Housholder 法 と等価である。
0x3fffffffff)error(-1/4..-1/2 LSB) = 68719476736
error(-1/4..+1/4 LSB) = 137438953472
error(+1/4..+1/2 LSB) = 68719476736
sqrt64 -- 与えられた 64-bit 整数の平方根に対して誤差が最小となる整数を返す。 負数の数に対しては、例外を発生させずそのまま返す。
/* sqrt64 - calculate squre root of given integer value * Rev.1.4 (2025-12-31) (c) 2003-2025 Takayuki HOSODA * SPDX-License-Identifier: BSD-3-Clause */ #include <inttypes.h> int64_t sqrt64(int64_t x); int64_t sqrt64(int64_t x) { int64_t a; // approximation register int64_t t; // scaled '1' int sr; // scaling (for relative domain of (0.25, 1)) int sx; // scaling (for small values) int n_bit = sizeof(x) * 8; // number of digits of given x (e.g. 64 for int64_t) int n_room = 8; // head room if (x <= 0) return x; // netative values are returned unchanged. sx = 0; a = x; if (x < (1LL << (n_bit / 2 - 1))) { // If x is in the lower half of the digits, sx = n_bit / 4; // note that square root halves the digits. a = x = (x << (n_bit / 2)); // enlarge to the upper half of the didits. } a >>= n_room; // scale down to get head-room sr = n_bit / 2 - n_room; t = 1LL << (n_bit - n_room - 2); while (a < t) { // find operating point t >>=2; sr--; } a >>= sr; // now, a / t is in (0.25, 1) t = 1LL << (sr + n_room); // scaled '1' a = (22 * a + 11 * t) / 32; // -3.1e-2 < err < +2.9e-2, a / t in (0.25, 1) a = (x / a + a) >> 1; // 0 <= err < 4.8e-4 a = (x / a + a) >> 1; // 0 <= err < 1.2e-7 a = (x / a + a) >> 1; // 0 <= err < 6.5e-15 if ((x - a) > (a * a)) // the approximation error is always zero or negative a++; // adjust LSB so that the error is within 1/2 LSB if (sx) { a += (1LL << (sx - 1)) - 1; // for round half a >>= sx; } return a; } #ifdef DEBUG #include <stdio.h> #include <math.h> int test_sqrt(int64_t x) { int64_t y; double ref; ref = round(sqrt((double)x)); y = sqrt64(x); fprintf(stdout, "sqrt64(%"PRId64") = %"PRId64" %s %.f\n", x, y, y != ref ? "!=" : "==", ref); return (y != ref ? 1 : 0); } int main(int argc, char *argv[]) { int i; int64_t x, y; if (argc == 2) { sscanf(&argv[1][0], "%" SCNd64 "\n", &x); y = sqrt64(x); fprintf(stdout, "sqrt64(%"PRId64") = %"PRId64"\n", x, y); } else { x = 1LL; for (i = 0; i < 63; i++) { test_sqrt(1LL << i); test_sqrt(x); x = (x << 1) | 1; } } return 0; } #endif
※ INT64_MAX (0x7fffffffffffffff) の平方根は、0xb504f334 (= 3037000500 ) となって、
INT32_MAX (0x7fffffff) よりも大きくなるため、戻り値を int32_t にすることは出来ません。
A1: 整数演算では、途中で値が 大きくなりすぎる からです。
平方根に対する Halley 法の更新式の一例は次の通りです。⛔ Halley 法は速いが、整数ではダイナミックレンジを消費しすぎる。
A2. 初期誤差は減りますが、代わりに中間量が肥大化します。
例えば、区間 x ∈ (0.25, 1) における 2 次の Min-Max 近似として、
⛔ 近似次数を上げると、ダイナミックレンジが一気に増大する。
A3. 可能ですが、その時点で負け。本末転倒です。
Newton–Raphson 法の更新式は、
✅ ニュートン法は速くはないが、スリムで堅牢で挙動も読める。
A4. 値域全体を、安全に、説明可能な形でカバーすることです。
本実装では、
結果として、
浮動小数点では「速い方法」が、✅ 付随的ですが、数値的信頼性も高く、シンプルで移植性も高いものとなっています。
整数演算では「危ない方法」になることがある。
この実装では、ダイナミックレンジを最優先に設計判断を行った。



© 2000 Takayuki HOSODA.