[英]What is the problem with this fixed point conversion from floating point in c?
我正在嘗試將浮點轉換為定點,因為硬件沒有浮點加速。 我似乎找不到變量screen_X
的錯誤,該值始終為 320.0。 精度為 Q38,26(編輯有用位為 Q6.26,64 位變量用於 mul 和 div 兼容性)。
#define fixed_precision 26
#define CONV_F(A) (A*conv_fixed_precision)
#define CONV_DF(A) (A/(1<<fixed_precision))
#define MUL38_26(A, B)((long long)(A*B)>>fixed_precision)
#define DIV38_26(A, B)((long long)(A<<fixed_precision)/B)
// global variable
double conv_fixed_precision = (1 << fixed_precision);
浮點代碼如下:
void draw_balls(VGA& vga) {
int i;
for (i = 0; i < NB_BALL; i++) {
if (R_Ball[i] == 0) continue; // the pearl has already been found
double screen_X = X_Ball[i];
double screen_Y = Y_Ball[i];
screen_X -= X_position;
screen_Y -= Y_position;
screen_X *= Scale;
screen_Y *= Scale;
double screen_Radius = R_Ball[i] * Scale;
if ((screen_X * screen_X + screen_Y * screen_Y <= screen_Radius * screen_Radius)) {
//La perle est trouvee
R_Ball[i] = 0;
int time = get_time();
printf("Found %i pearl(s) at time %i\n", ++found_pearl, time);
}
double tmp_X = screen_X;
double tmp_Y = screen_Y;
screen_X = (tmp_X * cosAngle - tmp_Y * sinAngle) * 240 + 320;
screen_Y = (tmp_X * sinAngle + tmp_Y * cosAngle) * 240 + 240;
screen_Radius *= 240;
if (screen_X + screen_Radius < 0) break;
if (screen_X - screen_Radius >= 640) break;
if (screen_Y + screen_Radius < 0) break;
if (screen_Y - screen_Radius >= 480) break;
drawBall3D(vga, screen_X, screen_Y, screen_Radius, i % 7);
}
#ifdef USE_OPEN_GL
glFlush();
#endif
定點代碼如下:
void draw_balls(VGA & vga) {
int i;
long long cosAngF = CONV_F(cosAngle);
long long sinAngF = CONV_F(sinAngle);
long long scaleF = CONV_F(Scale);
long long xPosF = CONV_F(X_position);
long long yPosF = CONV_F(Y_position);
for (i = 0; i < NB_BALL; i++) {
if (R_Ball[i] == 0) continue; // the pearl has already been found
long long screen_X = CONV_F(X_Ball[i]);
long long screen_Y = CONV_F(Y_Ball[i]);
screen_X -= xPosF;
screen_Y -= yPosF;
screen_X = MUL38_26(screen_X, scaleF);
screen_Y = MUL38_26(screen_Y, scaleF);
long long screen_Radius = CONV_F(R_Ball[i] * Scale);
if ((MUL38_26(screen_X, screen_X) + MUL38_26(screen_Y, screen_Y)) <= MUL38_26(screen_Radius, screen_Radius)) {
//La perle est trouvee
R_Ball[i] = 0;
int time = get_time();
printf("Found %i pearl(s) at time %i\n", ++found_pearl, time);
}
long long tmp_X = screen_X;
long long tmp_Y = screen_Y;
long long F240 = ((long long)240)<<fixed_precision;
long long F320 = ((long long)320) << fixed_precision;
screen_X = MUL38_26(MUL38_26(tmp_X, cosAngF) - MUL38_26(tmp_Y, sinAngF), F240) + F320;
screen_Y = MUL38_26(MUL38_26(tmp_X, sinAngF) + MUL38_26(tmp_Y, cosAngF), F240) + F240;
screen_Radius = MUL38_26(screen_Radius, F240);
long long F640 = ((long long)640) << fixed_precision;
long long F480 = ((long long)480) << fixed_precision;
if (screen_X + screen_Radius < 0) break;
if (screen_X - screen_Radius >= F640) break;
if (screen_Y + screen_Radius < 0) break;
if (screen_Y - screen_Radius >= F480) break;
drawBall3D(vga, screen_X>>fixed_precision, screen_Y >> fixed_precision, screen_Radius >> fixed_precision, i % 7);
}
#ifdef USE_OPEN_GL
glFlush();
#endif
#endif
}
#defines
中的\
是錯誤的
\
告訴您的編譯器定義在下一行繼續,但您的定義是單行的,這樣您的代碼就會出錯,甚至無法編譯。 我敢打賭,您是在基於 GCC 的編譯器上的某些 MCU 或 DSP 上執行此操作,通常錯誤不會生成新的二進制文件,因此您很可能使用過去最后可編譯的舊版本進行編程(有時很難將其發現為消息 build has stop 與編譯控制台中的成功操作區別不大)。
你得到了38.26
,它增加了 64 位並使用long long
問題是 long long 可以是任何東西,所以它在您的環境中有多少位? 如果只是 64 位,那么您會遇到問題,因為:
A<<fixed_precision
將需要64+fixed_precision
位來存儲結果,所以如果你得到的結果更少,那么你扔掉 A 的 MSB 位會使你的結果無效(除法)
這可以在 64 位上完成而不會丟失精度。 這里是我通常實現的定點數學:
m = 1<<n Integer(a) = Real(x*m) Real(x) = Integer(a)/mx*m + y*m = (x+y)*m -> (a+b) x*m - y*m = (xy)*m -> (ab) x*m * y*m = (x*y)*m*m -> (a*b)>>nx*m / y*m = (x/y) + (x%y)/y -> (a/b)<<n + ((a%b)<<n)/b
但是請注意,乘法a*b
仍然需要 128 位,因此如果您無法像乘法一樣訪問 ALU 或將其移植到 64位,請使用天真的O(n^2)
或 Karatsuba 乘法。 另一種選擇是將a*b>>n
分解為除法( >>n
)和模( &(n-1)
)部分,就像我對除法所做的那樣。 但是因為我可以在我編寫代碼的所有平台上訪問64b*64b=(64+64)b
操作,所以我懶得將其等同起來。
2'os 補碼上的有符號算術右移需要 SAR
如果您的號碼已簽名,則不可能天真地使用a>>n
,因為它很可能會破壞您的號碼符號。 要在 2'os 補碼上除以 2 的冪,您需要 SAR(算術右移),因此您需要復制 MSB 位,因此對於 1 位移位,請執行以下操作:
(a>>1)|(a&0x8000000000000000);
對於更多位移,您需要添加分支...例如 4,8,16 位:
(a>> 4)|((a&0x8000000000000000)?0xF000000000000000:0); (a>> 8)|((a&0x8000000000000000)?0xFF00000000000000:0); (a>>16)|((a&0x8000000000000000)?0xFFF0000000000000:0);
掩碼可以存儲在 LUT 中,或者您可以在循環中使用 1 位移位,但這會更慢。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.