能不能用double去取代float?

我剛剛學,他們一個單精度一個雙精度,聽起來double完全能勝任float的工作啊,那我能不能不用float只用double?


@迪迦奧特曼 說是體系結構知識的缺失,說的有點重了。

對於初學者來說,理解double和single precision在性能上的區別應該算是一種不需要體系結構就能理解的常識 —— 畢竟size又大,精度又高,費事兒是肯定的。那如果不考慮費事兒這件事情,能不能圖方便就用double替代single precision float呢?當然是可以的,而且很多的計算軟體也確實就這麼做的。

實際上體系結構的知識,主要是用來解釋「為什麼double比float要慢」這個問題而已,但是不是什麼時候我們都需要知道它到底是為什麼。

另外說double比float吞吐多一倍的,這不能算是一個原理性的東西,更多的是工程上的trade off。比如說如果某個缺心眼的硬體只有double,float也用double算,那基本上這兩者的throughput的區別就會非常小。

不過現實中因為double的計算單元太昂貴了,IO也要翻倍,很多時候float就足夠了,再加上二者的data path能夠復用,這幾個因素加一起才是為什麼double的吞吐量比float少的主要原因。


看到各位的評論下各種吐槽吐槽吐槽,我乾脆在微信上開個體系結構吐槽群好了。。。

要來吐槽的業內人士可以私信我你的微信號,哈哈哈哈哈哈。

(收到不少要加群的申請。我聲明下,這個群不是啥學習群,都在放毒,基本上主題就是硬體不行體系結構藥丸,趕快跳槽當AI碼農。所以不是行業內的我一般就不放進來棄明投暗了,實在不好意思。。。【笑哭】)


float 通常是使用 IEEE 754 標準中的 binary32 表示,以二進位方式、用32位存儲浮點數。

double 通常是 binary64。

其實 IEEE 754 標準中還有較少用到的 binary16(常稱為 half)、binary128(quadruple)和 binary256(octuple)。在 x86 架構上 long double 通常是 80 位的浮點數(不是 IEEE 754 標準)。

用更高精度就可表示更廣範圍、更精確的數字,但要付出存儲空間和運算速度的代價。

如果只是幾個變數、做幾個計算可能問題不大,但如果考慮海量數據和計算量,就要選擇最合適的類型。


基本贊同 @迪迦奧特曼 的回答。

  1. 關於float和double的「快慢」,大家問的時候,這個「快慢「可能意思是latency,也可能意思是throughput。所以一定要搞清楚問的是什麼。
    1. latency:double和single arithmetic在CPU上latency(也就是cycle)是一樣的。
    2. throughput來說,因為CPU上SIMD unit的寬度是一定的,所以double(佔用相當於float兩倍的寬度)的throughput是single的一半。在GPU上,因為double unit DP和single unit SP的比例不一樣,這個throughput的比例也不一定,如Volta上是1:2,K40上是1:3,而Titan X等消費卡則完全沒有DP unit就不能做double運算。
  2. 關於是否能取代,答案是看具體的應用。舉個例子,1+ 2^24 和2^24 兩個整數,用float表示的話,它們的表示是一樣的。而用double可以精確表示這兩個不同的數字。這種rounding error造成了浮點數運算是non associative的,也就是(a+b)+c與a+(b+c)不一樣。如果這是你可以接受的,那float就是夠用的。 (反過來說,對有些應用來說,double也是不夠的,需要long double甚至更多)
  3. 實踐上,對於精度要求較高的操作,如矩陣求逆、解線性方程組等,一般要求double。(事實上,超級計算機的benchmark linpack就是比用double精度來解線性方程組的浮點 throughput--flops)。而隨機梯度下降等演算法,用single甚至half precision也可以(因為反正這個梯度也是在隨機選取的樣本上的了)。


有一些人用float主要是因為double讓GPU的throughput降低了一半(逃

但是既然你剛學,你不會有什麼特殊需求的,用double總是沒有錯。特別是如果你寫的32位程序,FPU裡面用的基本是10位元組/16位元組的浮點,你用啥都一樣。64位雖然強行使用了SSE的寄存器,但是大多數情況下也沒辦法用SSE做並行化,也沒啥區別。


x86 CPU的標量浮點數計算float和double是用的相同的部件,所以性能上是沒差別的。但如果是SIMD矢量計算,float要比double多一倍,比如SSE一次算4個float或2個double,那float的吞吐量就是double的2倍。另外,double需要更大的存儲空間,需要大量浮點數據存儲的應用,比如3D渲染,那當然是首選float的。

所以這個問題的答案是不考慮SIMD矢量化也不在乎數據存儲空間的限制,在x86上用double和float基本沒區別。


對於初學者來說,建議只使用double,不要糾結double和float的區別。除非筆試要考,要求你背一些書本上的東西,上機一律用double不會有問題。學習編程要首先學會把要求的程序編寫出來,之後才是那些細節問題。

float會比double計算速度快,佔用空間少,但是精度比較低。如果不是運算瓶頸,那麼使用double就好了,不需要擔心因為float精度不夠啊所產生的問題。


真是難怪前幾天看到業界的前輩吐槽,說靠譜的體系結構的人難招。。。

double和float哪個快這麼簡單的問題,結果整個看下來只有 @高洋 叔和少數幾個答案靠譜啊媽蛋。

我來給個正解。

如果你的代碼就是輕量級地算一些很簡單的浮點數值,比如double 1.0 + 1.0,那single和double一樣快沒區別,因為底下的datapath是復用的。

同理,如果循環測試double和single的簡單串列操作,也是一樣的結果。也是因為底下的datapath是復用的。

如果你的代碼是浮點密集型,特別是打算上GPU,或者在CPU一側上SSE/AVX這些SIMD操作的話,double的速度最多達到single的一半,如果double速度超過single一半,那一定是single的datapath設計有問題,或者double和single走了兩套datapath,但這是效率很低下的設計。

原因也很簡單。

SIMD寄存器的寬度是固定的,Load/Store Unit的各種寬度,cache crossbar上跑的cache訪問packet,寬度都是物理上固定的,比如一次訪問cache最多取上來一個cache block 64Bytes,裡面最多8個double,但是single的話最多就有16個。它們用同一套ALU的話,一條SIMD single指令從前端到後端跑完花n cycles,一口氣算完了x個single浮點操作,那麼double指令從前端到後端同樣跑完n cycles,就只能算x/2個double浮點操作。

SSE/AVX的single和double,GPU的single/double吞吐有多大差距,隨便寫個代碼就能驗證了,自己懶得寫可以去看各種benchmark的數值啊。現在AI演算法都開始跑16位甚至8位精度的數字了。。。

==========================================================

真是沒有想到連SIMD的速度差距都有人不相信

直接從人家博客上的模板代碼改了一個版本下來,自己改一下F_TYPE就可以測試100m個float和100m個double在AVX2下累加帶來的性能差別。

我的機器(3.4GHz Skylake)上100m個float耗時0.09s,100m個double耗時0.17s。

#include &

#include &

#include &

#define SIZE 100000000

typedef float F_TYPE;

typedef unsigned long long int uint64;

double sum_double_avx(const double* pbuf, uint64 cntbuf)

{

double s = 0;

uint64 nBlockWidth = 4;

uint64 cntBlock = cntbuf / nBlockWidth;

uint64 cntRem = cntbuf % nBlockWidth;

__m256d xfdSum = _mm256_setzero_pd();

__m256d xfdLoad;

const double* p = pbuf;

const double* q;

//AVX

for(uint64 i=0; i&

{

xfdLoad = _mm256_load_pd(p);

xfdSum = _mm256_add_pd(xfdSum, xfdLoad);

p += nBlockWidth;

}

q = (const double*)xfdSum;

s = q[0] + q[1] + q[2] + q[3];

for(uint64 i=0; i&

{

s += p[i];

}

return s;

}

float sum_single_avx(const float* pbuf, uint64 cntbuf)

{

float s = 0;

uint64 nBlockWidth = 8;

uint64 cntBlock = cntbuf / nBlockWidth;

uint64 cntRem = cntbuf % nBlockWidth;

__m256 yfsSum = _mm256_setzero_ps();

__m256 yfsLoad;

const float* p = pbuf;

const float* q;

//AVX

for(uint64 i=0; i&

{

yfsLoad = _mm256_load_ps(p);

yfsSum = _mm256_add_ps(yfsSum, yfsLoad);

p += nBlockWidth;

}

q = (const float*)yfsSum;

s = q[0] + q[1] + q[2] + q[3] + q[4] + q[5] + q[6] + q[7];

for(uint64 i=0; i&

{

s += p[i];

}

return s;

}

int main()

{

F_TYPE* array = (F_TYPE*)malloc(sizeof(F_TYPE) * SIZE);

if(array == NULL) exit(-1);

F_TYPE sum = 0;

for(uint64 i = 0; i &< SIZE; i++)

{

array[i] = (F_TYPE)0.01;

}

if(sizeof(F_TYPE) == sizeof(double))

printf("sum = %f
", sum_double_avx(array, SIZE));

else printf("sum = %f
", sum_single_avx(array, SIZE));

free(array);

return 0;

}

上面0.17s的結果是double的,相加出來100m個0.01(double)相加出來是1000000.000323

下面0.09s左右的結果是single的,相加出來100m個0.01(single)是921697.562500,累積誤差比較大


計算能力允許的情況下,能用double就用double,省的面對一些蛋疼的偶爾會遇到的問題

而除非你做大規模數值計算,或者gpu開發,或者嵌入式,現在的計算資源基本都是過度的。寫軟體卡速度的從來都是io。。。。


這得從兩方面說。

通常 C/C++ 編程時面向的是一台抽象的機器,C/C++ 語言本身也是圍繞著這台抽象機器定義的。在這台機器上,你不必考慮硬體問題,但你可能需要考慮這幾件事情:

  • 比如你可能需要遷就所用的庫函數參數類型(順便一提,標準庫里的一些數學函數最初都只有 double 的版本)
  • 比如代碼里 0.0 就是 double,其它浮點數類型都需要額外加後綴(比如 0.0f)
  • 比如你只能保證 float、double 的精度不是遞減的,甚至 float、double 可能都是 IEEE binary64 沒有區別

另一方面,隨著需求的深入,你可能開始需要進行大規模的浮點數據處理,你的程序繼而可能需要在現實世界中針對不同的硬體環境作出優化或者妥協,所謂的自有國情在此:

  • 比如處理大規模浮點數據時,吞吐量、緩存失效等問題就可能變得重要起來
  • 比如絕大多數顯卡,以目前的科技水平,處理 float 的速度是最快的(不過反正這種介面大多是要求 float 的,這在上一部分遷就庫函數參數類型里應該已經考慮過了)
  • 比如一些嵌入式設備,本身就只能直接處理 float,而 double 是需要模擬的(甚至任何浮點數都是模擬的,還不如用定點數)
  • 比如你需要使用一些特別的指令集來優化對浮點數的處理,而這些指令集對 float 更友好

總結一下,就是除非你有明確要求使用 float 的特殊場景,否則默認可以都用 double。像題目里說明自己是初學,一般是接觸不到這些場景的,直接用 double 就好了。對於更多的人來說,先把語義搞正確,考慮效率才有意義。

p.s. 有興趣的話還可以看看這篇超長的 What Every Computer Scientist Should Know About Floating-Point Arithmetic


這個問題下面一半答案在說吞吐量,另一半在說延遲

當討論「性能」的時候,有時候必須得分成延遲(處理單個任務的時間)和吞吐量(單位時間處理的任務量)兩方面來討論

簡單結論是,現代常規的計算硬體(桌面/移動端CPU,GPU)上面double和float的延遲差不多,吞吐量float高一倍

如果你的代碼就是想拿個實數簡單地算來算去,那用double還是用float性能都差不多

如果你的代碼裡面有大規模的實數運算,例如向量求和/矩陣乘/歸約,請根據精度需求能用float就別用double,再找一個靠譜的優化的較好的庫如Eigen / OpenBLAS / MKL / ArmComputeLibrary / CUDA全家桶

影響延遲的是關鍵路徑的耗時,影響吞吐量的是堆料的規模。CPU/GPU/內存發展到現在,基本上延遲改進的已經非常緩慢了,仍在快速提高的是吞吐量。處理器搞那麼多級流水線和那麼寬的SIMD,CPU堆那麼多核心,GPU搞那麼多的stream processor,多半都是提升吞吐量但是不降低延遲的方案。甚至DDR內存一代一代往上更新也是帶寬在快速提升而訪問延遲改進的不多。

double和float的關鍵路徑耗時差不多,所以它們的延遲差不多;但是float位寬只有double的一半,大多數硬體裡面float和double的datapath又是幾乎完全復用的,也即堆的料一樣多,所以float的吞吐量通常是double的兩倍,現今主流的Intel和ARM CPU都是如此。當然也有些硬體上面它們datapath不完全復用,比如Nvidia的有些GPU,double的吞吐量只有float的幾十分之一,移動端的有些GPU乾脆都不支持double

不過倒是還沒見過double吞吐量超過float吞吐量的一半的硬體...


之前看到有人說新手很多問題都可以看一遍 《C++ Primer》 解決, 現在我信了..

請翻到 P32 中間:

執行浮點數運算選用 double, 這是因為 float 通常精度不夠而且雙精度浮點數和單精度浮點數的計算代價相差無幾. 事實上, 對於某些機器來說, 雙精度運算甚至比單精度還快. long double 提供的精度在一般情況下是沒有必要的, 況且它帶來的運行時消耗也不容忽視.


浮點吞吐量和內存緩存佔用上,double會有一倍的差距… 在大多數GPU上差距甚至更大的多… 延遲在某些運算上相同,有些double更慢。所以不需要高精度的話,盡量用float


不管是 float 還是 double,哪怕是 IEEE 的浮點數標準,本身都是有明顯設計缺陷的。

簡單說:1. 大概率上浮點數的表達是有偏差的,哪怕只是有理數範圍內也都是;2. 浮點數表達精度,無法根據位長靈活調整,只能由數據格式固定的 eps 決定。

而這兩點,都是可以很容易解決的,只是歷史遺留太重。

解決方案還不是說從 32 位、64 位,變成 8 位、128 位。那還是走邪路。而是應該換一種思路來做。

簡單說,是用有理數的數學表示構造一個基本數據類型。但其實這一思路也還有很多坑。但有人寫了本書,詳細論證了這個設計。然後兩年前,有人用julia實現了這個設計,經過驗證,也確實能達到設計目的。

其實哪怕只是部分解決這個問題,社會整體的工程實踐水平都能上一個台階。大體上數值計算的效率能提升至少兩個數量級。

未來的希望,還是在 Julia 啊。

希望能有空看完新浮點數設計的那本書,看完再回來詳答。


補充:一時半會兒確實沒時間去翻書,轉一段之前寫的視頻總結。裡面各種資料的鏈接都全,供參考。

25. Unums 2.0 | Jason Merrill

Youtube: https://www.youtube.com/watch?v=t-LIXuhUes8

鏈接: https://pan.baidu.com/s/1c2zuC8o 密碼: nir3(早先鏈接錯了,不好意思)

這個講座主要介紹一個基礎的數論和浮點計算工具。在遇到 0 點的時候,計算機總是會有問題,例如「+0」、「-0」的問題(就是很小很小的正數和負數),在整數附近的浮點數也會遇到同樣的問題。

這個講座的作者 Jason Merrill 需要開發維護一個中小學數學可視化的項目。就是給定一個函數,畫出函數圖像。說起來簡單,但經常會遇到各種數值計算的問題。就是跨零點和函數連續性的問題。

解決的方法,主要是 Unums 2.0 這個計算機數字表示工具。Unums 1.0 來自一本《The End of Error: Unum Computing》(作者 John I. Gustafson)。有關 Unums 2.0 的書還沒發表。而本講座的主講開發了一個弱化的 Pnums.jl 包( https://github.com/jwmerrill/Pnums.jl )來做這件事。

Unums 的作者 Dr. Gustafson(古斯塔夫遜博士?)在 2015 年有個介紹的講座,簡潔扼要地介紹了有關 Unums 的工作( http://arith22.gforge.inria.fr/slides/06-gustafson.pdf )。簡單講,Unums 1.0 表達出精確值之間的開集。這樣有理集(實數集?沒看到無理數的例子)可以精確描述。Unums 2.0 在表達有理數集時,把正負無窮合併為一個點。這樣實數集就不是一個兩端無窮遠的直線數軸,而是一個在正負無窮遠點相接的有理數環。為此,引入一個倒數表達法。即無窮大表達為零的倒數 /0。所有數的倒數都可以精確表達 /3、/4=0.25、/5=0.2 等。這種方法表達數值的精確度,直接依賴於使用的二進位字長,最短三位(3bit)就可以(pb3)。

這個工具還是非常有用的。對於主講的主業,函數可視化來說,一個函數可視化工具必然要把計算細節隱藏起來。對於傳統的計算機教育,面向大學生或以上,在對於數值計算有了一定理解之後,對於計算誤差還是可以理解的。但現在不論是數學教育還是計算機教育都低齡化、普及化、義務教育化,非常需要能夠完美隱藏這些醜陋細節的可視化工具。

對於數值計算的基礎,說起來也確實慚愧,這麼多年的計算機發展,基本的浮點數的精度還是一個經常遇到很尷尬,還是一個影響嚴重的問題。這點真的需要從基礎上做出改進。一個直接的問題,就是矩陣運算中解的數值誤差,對於工程應用影響廣泛。從這一點說,Unums 真的是一個革命性的發明。我非常好奇 Unums 對優化、對數值計算、對矩陣分解的影響。也許會為工程計算、機器學習帶來革命性的性能效率提升。

主講最後總結 Julia 簡單的泛型編程、方便的運算符重載,直接的上層與低層的溝通使得 Pnums.jl 這種針對低層的浮點數表達的包的開發變得很容易。


轉眼就是個相關的問題:發現c++漏洞,你們也可以試一試,在if語句中,輸入1.1 2.2 3.3,系統的計算判斷會出現錯誤?


已經有好幾門語言這麼幹了


某些情況下,為了簡單是可以的。比如Perl總是使用平台上最高精度的浮點作為它scalar裡面的小數類型。

不過但凡你對效率有一些要求,就不能這麼替換,因為double比single的計算速度(在常見CPU上)通常都會慢一倍。然後還有緩存命中、內存帶寬什麼的問題。


float做不到6位十進位有效數字


這個有很多類似的問題。為什麼不用long取代int?用matrix取代vector?

全部都是性能的tradeoff啊。

殺雞用宰牛刀也是這個意思。


結論:向量處理時float 和double才有明顯差別。

普通人寫兩個數組相加C = A+B;

寫成for(int i = 0;i &< N;i++) C[i] = A[i] + B[i];用float 和double差別不大

但是如果用SSE加速_mm_add_ps 一次能加4個float,但是只能加2個double,速度就有差別了。


說下我自己寫代碼時的基本原則,主要參考opencv及其他優秀計算庫:

1 大部分中間計算用double,保證精度,如必要,結果再轉float(如下情況)。

2 可simd計算且32 bit精度夠用的話,用float。

3 存儲或io傳輸有壓力且精度夠用時,用float

4 跨平台開發時,為使結果與simd開啟時保持一致,強制用float,降低精度。


拜託那些各種證明float比double快的專家,能不能給段代碼,只要float比double快,只要一個就行。

以下為原答案:

64位用double,float在傳參會轉double,反而慢。

lua直接用double代替整型,所有數字都是double

x64 gcc編譯器計算的時候float會轉為double

用double:
x# gcc -O2 t3.c
x# ./a.out
x# time ./a.out

real 0m1.397s
user 0m1.396s
sys 0m0.002s

用float
x# gcc -O2 t3.c
x# time ./a.out

real 0m3.620s
user 0m3.619s
sys 0m0.004s

/*t3.c*/
#include &
int main()
{
long int nr=1000000000;
float f=0.1;//double f=0.1;
long int i=0;
while(i++&


推薦閱讀:

想自學編程怎樣下手?
如何評價中科大軟院的孟寧老師?
女生適合學軟體工程嗎?
為什麼一些程序員連 Visual Studio 怎麼卸載都不會?
如何高效地增強編程(特別是debug)能力?

TAG:軟體工程 | 計算機科學 | CC |