1 /*---------------------------------------------------------------------------*
2 Project: Horizon
3 File: math_Equation.cpp
4
5 Copyright (C)2009 Nintendo Co., Ltd. All rights reserved.
6
7 These coded instructions, statements, and computer programs contain
8 proprietary information of Nintendo of America Inc. and/or Nintendo
9 Company Ltd., and are protected by Federal copyright law. They may
10 not be disclosed to third parties or copied or duplicated in any form,
11 in whole or in part, without the prior written consent of Nintendo.
12
13 $Rev: 12449 $
14 *---------------------------------------------------------------------------*/
15
16 #include <nn/math.h>
17
18 #include <nn/math/math_Equation.h>
19 #include <nn/math/math_Arithmetic.h>
20 #include <nn/math/math_Triangular.h>
21 #include <nn/types.h>
22
23 namespace nn { namespace math {
24
25
26 namespace
27 {
28 // 等しいと判定する最大誤差
29 const f32 EPS = 2e-4f;
30
31
32 /*!--------------------------------------------------------------------------*
33 Name: spow<int>
34
35 @brief 対象の数の自然数乗を求めます。
36 最適化によって静的なコードが生成される事を期待します。
37
38 @param[in] idx 指数です。定数で指定しなければなりません。
39 @param[in] x 底です。
40
41 @return x の idx 乗を返します。
42 *---------------------------------------------------------------------------*/
43 template <int idx>
44 NN_MATH_INLINE f32
spow(f32 x)45 spow(f32 x)
46 {
47 return spow<idx-1>(x) * x;
48 }
49
50 template <>
51 NN_MATH_INLINE f32
spow(f32 x)52 spow<1>(f32 x)
53 {
54 return x;
55 }
56
57 /*!--------------------------------------------------------------------------*
58 Name: SolveEquation2
59
60 @brief 二次方程式 x^2 + bx + c = 0 の実数解を求めます。
61 SolveEquation4 用です。
62
63 @param[out] root 実数解を格納する配列へのポインタ。
64 二次方程式には最大で2つの実数解が存在しますので、
65 配列の大きさは2以上でなければなりません。
66 @param[in] b,c 対象の二次方程式の係数。
67
68 @return 実数解の個数。
69 root が指す配列の先頭から順に個数分の解が格納されます。
70 *---------------------------------------------------------------------------*/
71 int
SolveEquation2(f32 * root,f32 b,f32 c)72 SolveEquation2(f32* root, /*f32 a==1,*/ f32 b, f32 c)
73 {
74 if( b == 0 )
75 {
76 if( c < -EPS ) // c < 0
77 {
78 f32 r_c = FSqrt(-c);
79 root[0] = r_c;
80 root[1] = - r_c;
81 return 2;
82 }
83 else if( c <= EPS ) // c == 0
84 {
85 root[0] = 0;
86 return 1;
87 }
88 else // c > 0
89 {
90 return 0;
91 }
92 }
93 else
94 {
95 f32 A = b / 2;
96 f32 B = c / spow<2>(A);
97 f32 D = 1 - B;
98
99 if( D > 0 )
100 {
101 f32 C = -1 - FSqrt(D);
102
103 root[0] = A * C;
104 root[1] = A * B / C;
105
106 return 2;
107 }
108 else if( FAbs(D) < EPS )
109 {
110 root[0] = -A;
111
112 return 1;
113 }
114 else
115 {
116 return 0;
117 }
118 }
119 }
120
121
122
123 /*!--------------------------------------------------------------------------*
124 Name: SolveEquation3
125
126 @brief 三次方程式 x^3 + bx^2 + cx + d = 0 の実数解を1つ求めます。
127 SolveEquation4 用です。
128
129 @param[in] b,c,d 対象の三次方程式の係数。
130
131 @return 実数解のうちのどれか1つの値。
132 *---------------------------------------------------------------------------*/
133 f32
SolveEquation3(f32 b,f32 c,f32 d)134 SolveEquation3(/*f32* root, f32 a==1,*/ f32 b, f32 c, f32 d)
135 {
136 f32 q = (spow<2>(b) - 3 * c) / 9;
137 f32 r = (2 * spow<3>(b) - 9 * b * c + 27 * d) / 54;
138 f32 D = spow<3>(q) - spow<2>(r);
139 f32 b_3 = b / 3;
140
141 if( D > EPS )
142 {
143 f32 theta = AcosRad( r / FSqrt(spow<3>(q)) );
144 f32 theta_3 = theta/3;
145 f32 r_q2 = -2 * FSqrt(q);
146
147 return r_q2 * CosRad(theta_3) - b_3;
148 }
149 else if( D < - EPS )
150 {
151 f32 r3_Dr = FCbrt( FSqrt(- D) + FAbs(r) );
152 f32 xp = r3_Dr + q / r3_Dr;
153
154 return - FCopySign(xp, r) - b_3;
155 }
156 else
157 {
158 f32 xp = FSqrt(q);
159
160 return FCopySign(xp, r) - b_3;
161 }
162 }
163 }
164
165
166
167
168
169
170
171 /*!--------------------------------------------------------------------------*
172 Name: SolveEquation2
173
174 @brief 二次方程式 ax^2 + bx + c = 0 の実数解を求めます。
175
176 @param[out] root 実数解を格納する配列へのポインタ。
177 二次方程式には最大で2つの実数解が存在しますので、
178 配列の大きさは2以上でなければなりません。
179 @param[in] a,b,c 対象の二次方程式の係数。
180 a が 0 であってはいけません。
181
182 @return 実数解の個数。
183 root が指す配列の先頭から順に個数分の解が格納されます。
184 *---------------------------------------------------------------------------*/
185 int
SolveEquation2(f32 * root,f32 a,f32 b,f32 c)186 SolveEquation2(f32* root, f32 a, f32 b, f32 c)
187 {
188 NN_POINTER_TASSERT_(root);
189 NN_TASSERT_( a != 0 );
190
191 if( b != 0 )
192 {
193 // 一般的な解の公式
194 // x = {-b ± √(b^2 - 4ac)} / 2a
195 // では -b ± √(略) の部分で -b の正負と ± の正負が異なる方の解を
196 // 求める時に、近しい値の減算が発生し精度が大きく落ちます。
197 // これを回避するため、解の一方を
198 // x = b{-1 - √(1 - 4ac/b^2)} / 2a
199 // と b を括りだして求めます。
200 // 他方の解には、上記式に解と係数の関係
201 // αβ = a / c
202 // を適用して
203 // x = 2c / b{-1 - √(1 - 4ac/b^2)}
204 // から求めます。
205
206 f32 A = b / (2 * a);
207 f32 B = c / (a * A * A);
208 f32 D = 1 - B;
209
210 if( D > EPS )
211 {
212 f32 C = -1 - FSqrt(D);
213
214 root[0] = A * C;
215 root[1] = A * B / C;
216
217 return 2;
218 }
219 else if( D >= - EPS )
220 {
221 root[0] = -A;
222
223 return 1;
224 }
225 else
226 {
227 return 0;
228 }
229 }
230 else
231 {
232 // 上の方法では b = 0 の場合に 0 除算が発生するため場合分けします。
233
234 f32 c_a = - c / a;
235
236 if( c_a > EPS )
237 {
238 f32 r_c_a = FSqrt(c_a);
239 root[0] = + r_c_a;
240 root[1] = - r_c_a;
241 return 2;
242 }
243 else if( c_a >= - EPS )
244 {
245 root[0] = 0;
246 return 1;
247 }
248 else
249 {
250 return 0;
251 }
252 }
253 }
254
255
256
257 /*!--------------------------------------------------------------------------*
258 Name: SolveEquation3
259
260 @brief 三次方程式 ax^3 + bx^2 + cx + d = 0 の実数解を求めます。
261
262 @param[out] root 実数解を格納する配列へのポインタ。
263 三次方程式には最大で3つの実数解が存在しますので、
264 配列の大きさは3以上でなければなりません。
265 @param[in] a,b,c,d 対象の三次方程式の係数。
266 a が 0 であってはいけません。
267
268 @return 実数解の個数。
269 root が指す配列の先頭から順に個数分の解が格納されます。
270 *---------------------------------------------------------------------------*/
271 int
SolveEquation3(f32 * root,f32 a,f32 b,f32 c,f32 d)272 SolveEquation3(f32* root, f32 a, f32 b, f32 c, f32 d)
273 {
274 NN_POINTER_TASSERT_(root);
275 NN_TASSERT_( a != 0 );
276
277 //---- 両辺を最高次の係数で割って最高次の係数を 1 にします
278 b /= a;
279 c /= a;
280 d /= a;
281
282 //---- 判別式 D の値を求めます
283 f32 q = (spow<2>(b) - 3 * c) / 9;
284 f32 r = (2 * spow<3>(b) - 9 * b * c + 27 * d) / 54;
285 f32 D = spow<3>(q) - spow<2>(r);
286
287 f32 b_3 = b / 3;
288
289 if( D > EPS )
290 {
291 f32 theta = AcosRad( r / FSqrt(spow<3>(q)) );
292 f32 theta_3 = theta/3;
293 f32 r_q2 = -2 * FSqrt(q);
294
295 root[0] = r_q2 * CosRad(theta_3 + 0 * F_PI / 3) - b_3;
296 root[1] = r_q2 * CosRad(theta_3 + 2 * F_PI / 3) - b_3;
297 root[2] = r_q2 * CosRad(theta_3 + 4 * F_PI / 3) - b_3;
298
299 return 3;
300 }
301 else if( D < - EPS )
302 {
303 f32 r3_Dr = FCbrt( FSqrt(- D) + FAbs(r) );
304 f32 xp = r3_Dr + q / r3_Dr;
305
306 root[0] = - FCopySign(xp, r) - b_3;
307
308 return 1;
309 }
310 else
311 {
312 f32 xp = FSqrt(q);
313 f32 sxp = FCopySign(xp, r);
314
315 root[0] = 1 * sxp - b_3;
316 root[1] = -2 * sxp - b_3;
317
318 return 2;
319 }
320 }
321
322
323
324 /*!--------------------------------------------------------------------------*
325 Name: SolveEquation4
326
327 @brief 四次方程式 ax^4 + bx^3 + cx^2 + dx + e = 0 の実数解を
328 求めます。
329
330 @param[out] root 実数解を格納する配列へのポインタ。
331 四次方程式には最大で4つの実数解が存在しますので、
332 配列の大きさは4以上でなければなりません。
333 @param[in] a,b,c,d,e 対象の四次方程式の係数。
334 a が 0 であってはいけません。
335
336 @return 実数解の個数。
337 root が指す配列の先頭から順に個数分の解が格納されます。
338 *---------------------------------------------------------------------------*/
339 int
SolveEquation4(f32 * root,f32 a,f32 b,f32 c,f32 d,f32 e)340 SolveEquation4(f32* root, f32 a, f32 b, f32 c, f32 d, f32 e)
341 {
342 NN_POINTER_TASSERT_(root);
343 NN_TASSERT_( a != 0 );
344
345 f32 m, n, y;
346
347 //---- 両辺を最高次の係数で割って最高次の係数を 1 にします
348 b /= a;
349 c /= a;
350 d /= a;
351 e /= a;
352
353 //---- 平方完成
354 {
355 // t = x + b/4 と置くと x = t - b/4
356 // これを x に代入して整理すると3次の項が消えて、
357 // t^4 + pt^2 + qt + r = 0
358 // となる。ここで p, q, r はそれぞれ以下のとおり。
359
360 f32 p = - 3.f * spow<2>(b) / 8 + c;
361 f32 q = spow<3>(b) / 8 - b * c / 2 + d;
362 f32 r = - 3.f * spow<4>(b) / 256 + spow<2>(b) * c / 16 - b * d / 4 + e;
363
364
365 // t に関する式を以下の形に平方完成させる事を考える
366 // (t^2 + y)^2 = (mt + n)^2 ------- (1)
367
368 if( q != 0 )
369 {
370 // まず t^2 に関して任意の値 y について
371 // (t^2 + y)^2 = 2yt^2 + y^2 - (pt^2 + qt + r)
372 // = (2y - p)t^2 + qt + (y^2 - r) ------- (2)
373 // と平方完成させる事ができる
374 // さらに右辺も平方完成させるには右辺の二次式の判別式 D = 0 であればよいので
375 // q^2 - 4(2y - p)(y^2 - r) = 0
376 // を満たす y を求めればよい。 y に関して整理すると
377 // y^3 - (p/2)y^2 - ry + (pr/2 - (q^2)/8) = 0
378 // この三次式を解いて解の一つを y として採用する。
379
380 y = SolveEquation3(-p / 2, -r, p * r / 2 - spow<2>(q) / 8);
381
382
383 // (1)式の右辺を展開すると
384 // (mt + n)^2 = m^2t^2 + 2mnt + n^2
385 // これを(2)式と比較して以下を得る
386
387 n = FSqrt(spow<2>(y) - r);
388 m = -q / (2 * n);
389 }
390 else
391 {
392 // q = 0 のとき上記三次方程式を解くと y = p/2, ±sqrt(r)
393 // SolveEquation3 によって ±sqrt(r) が選ばれると n = 0 となり、
394 // 0 除算が発生する。
395 // さらに p = q = r = 0 の場合必ず n = 0 となる。
396 // これらを回避する。
397
398 y = p / 2;
399 n = FSqrt(spow<2>(y) - r);
400 m = 0;
401 }
402 }
403
404 // 与式を(1)式の形に変形できたので、これを解く。
405 // (1)式の両辺の平方根を取ると
406 // t^2 + y = ±(mt + n)
407 // これは 2 つの二次式
408 // t^2 + mt + (y + n) = 0
409 // t^2 - mt + (y - n) = 0
410 // を表すので、これらを解けば t の値が求まる。
411 // さらに x = t - b/4 より x が求まる。
412
413 int nRoot = 0;
414 f32 b4 = b / 4;
415
416 //---- 二次方程式1
417 {
418 f32 root01[2];
419
420 int nRoot01 = SolveEquation2(root01, m, y + n);
421
422 root[nRoot] = root01[0] - b4;
423 root[nRoot+1] = root01[1] - b4;
424 nRoot += nRoot01;
425 }
426
427 //---- 二次方程式2
428 {
429 f32 root23[2];
430
431 int nRoot23 = SolveEquation2(root23, -m, y - n);
432
433 root[nRoot] = root23[0] - b4;
434 root[nRoot+1] = root23[1] - b4;
435 nRoot += nRoot23;
436 }
437
438 return nRoot;
439 }
440
441
442 }} // nw::math
443