在我剛接觸編程的時候, 那時候面試小題目很喜歡問下面這幾類問題 面試
1' 浮點數如何和零比較大小?編程
2' 浮點數如何轉爲整型?ubuntu
而後過了七八年後這類問題應該不多出如今面試中了吧. 恰好最近我遇到線上 bug, 同你們交流科普下app
問題最小現場佈局
#include <stdio.h> int main(void) { float a = 2.01f; double b = 2.01; printf("a1 : 2.01 * 1000 = %f\n", a * 1000); // a1 : 2.01 * 1000 = 2010.000000 printf("a2 : int(2.01 * 1000) = %d\n", (int)(a * 1000)); // a2 : int(2.01 * 1000) = 2010 printf("b1 : 2.01 * 1000 = %lf\n", b * 1000); // b1 : 2.01 * 1000 = 2010.000000 printf("b2 : int(2.01 * 1000) = %d\n", (int)(b * 1000)); // b2 : int(2.01 * 1000) = 2009 }
(用 Go Java 效果是同樣的, 絕大部分實現都是嚴格遵循 IEEE754 標準測試
問題解答spa
其中 a1 和 b1 在 C 中 等價於下面的代碼code
float a = 2.01f; double b = 2.01; printf("a1 : 2.01 * 1000 = %f\n", (double)(a * 1000)); printf("b1 : 2.01 * 1000 = %f\n", b * 1000);
其中 printf float 其實至關於 printf (double) 去處理的. 具體能夠看這類源碼 blog
#define PARSE_FLOAT_VA_ARG(INFO) \ do \ { \ INFO.is_binary128 = 0; \ if (is_long_double) \ the_arg.pa_long_double = va_arg (ap, long double); \ else \ the_arg.pa_double = va_arg (ap, double); \ } \ while (0)
其次兩者輸出打印的數據內容同樣. 本質緣由是, double 尾數的高23位和float的尾數23位同樣.內存
若是你用 %.8f 可能就不同了.
(float : 1 + 8 +23, 小數點後精度 6-7)
(double : 1 + 11 + 52, 小數點後精度 15-16)
簡單的, 咱們能夠用下面代碼去驗證
#include <stdio.h> static void print_byte(unsigned char byte) { printf("%d%d%d%d%d%d%d%d" , ((byte >> 7) & 1) , ((byte >> 6) & 1) , ((byte >> 5) & 1) , ((byte >> 4) & 1) , ((byte >> 3) & 1) , ((byte >> 2) & 1) , ((byte >> 1) & 1) , ((byte >> 0) & 1) ); } static void print_number(const void * data, size_t n) { const unsigned char * bytes = data; # if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ for (size_t i = n; i > 0; i--) { print_byte(bytes[i-1]); } # else for (size_t i = 0; i < n; i++) { print_byte(bytes[i]); } # endif } static void print_float(float num) { printf(" float = "); print_number(&num, sizeof num); printf("\n"); } static void print_double(double num) { printf("double = "); print_number(&num, sizeof num); printf("\n"); } int main(void) { float a = 2.01f; double b = 2.01; print_float(a); print_double(b); printf(" float 2.01f + %%.%df = %.*f\n", 8, 8, a); printf("double 2.01 + %%.%df = %.*lf\n", 8, 8, b); }
在 window 和 ubuntu 獲得的測試數據以下
/* float = 01000000000000001010001111010111 double = 0100000000000000000101000111101011100001010001111010111000010100 float 2.01f = 0 10000000 00000001010001111010111 double 2.01 = 0 10000000000 00000001010001111010111 00001010001111010111000010100 float 2.01f + %.6f = 2.010000 double 2.01 + %.6f = 2.010000 float 2.01f + %.7f = 2.0100000 double 2.01 + %.7f = 2.0100000 float 2.01f + %.8f = 2.00999999 double 2.01 + %.8f = 2.01000000 float 2.01f + %.10f = 2.0099999905 double 2.01 + %.10f = 2.0100000000 float 2.01f + %.15f = 2.009999990463257 double 2.01 + %.15f = 2.010000000000000 float 2.01f + %.16f = 2.0099999904632568 double 2.01 + %.16f = 2.0099999999999998 float 2.01f + %.17f = 2.00999999046325684 double 2.01 + %.17f = 2.00999999999999979 */
明顯能夠看出來 a = 2.01f 和 b = 2.01 在內存中兩者是不同的. 即 a != b, a * 1000 != b * 1000. 有興趣的能夠自行去實驗.
問題解答繼續
這裏說說 a2 和 b2 case 形成的緣由.
printf("a2 : int(2.01 * 1000) = %d\n", (int)(a * 1000)); // a2 : int(2.01 * 1000) = 2010 printf("b2 : int(2.01 * 1000) = %d\n", (int)(b * 1000)); // b2 : int(2.01 * 1000) = 2009
咱們首先獲取其內存佈局
float 2010.0f = 0 10001001 11110110100000000000000 double 2010.0 = 0 10000001001 1111011001111111111111111111111111111111111111111111
隨後藉助場外信息, 引述 <<深刻理解計算機系統-第三版>> 部分舍入概念
偏差來自浮點數沒法精確表示和轉換過程當中舍入起的效果.
問題反思
這類問題, 或多或少遇到過, 但願咱們這裏對這類問題作個告終 ~
此刻不知道有心人會不會着急下結論,
那之後的業務中仍是別用 float 了, 或者直接用 double, 或者定點小數, 或者整數替代 float 等等 ...
這麼考慮很不錯, 在大多數領域是徹底沒有問題的. 也是值得推薦的.
補充下, 也有些領域例如嵌入式, 他們仍是會用 float, 由於對他們而言 double 有的時候太浪費內存了,
還存在着地址對齊等問題.
雖然不一樣領域(場景)會有不一樣方式方法, 但有一點須要你們一塊遵照, 沒有特殊狀況別混着用 ~
但願以上能幫助朋友們對這類問題知其因此然 ~
後記 - 再見, 祝好運 ~
錯誤是不免的, 歡迎交流指正, 當找個樂子 ~ 哈哈哈 ~