日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

活动平台为何没有x86?_一个由跨平台产生的浮点数bug | 有你意想不到的结果

發布時間:2023/12/15 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 活动平台为何没有x86?_一个由跨平台产生的浮点数bug | 有你意想不到的结果 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

問題背景

背景就簡單點兒說,當初一個項目 C# 編寫,涉及浮點運算,來龍去脈省去,直接看如下代碼。(為什么有這個問題產生,是因為當初線上產生了很詭異的問題,和本地調試效果不一致。)

float?p3x = 80838.0f;
float?p2y = -2499.0f;
double?v321 = p3x * p2y;
Console.WriteLine(v321);

很簡單吧,馬上筆算下結果為 -202014162,沒問題,難道C#沒有產生這樣的結果?不可能吧,開啟 VisualStudio,copy代碼試試,果然結果是-202014162。就這樣完了么?顯然沒有!把編譯時的選項從AnyCPU改成x64試試~(服務器環境正是64位滴哦!!)結果居然變成了-202014160,對沒錯,就是-202014160。細想一下,因為浮點運算的誤差,-202014160 這個結果是合理的。嗯,再試試C++。// 測試環境Intel(R) i7-3770 CPU, windows OS 64. Visual Studio 2012 默認設置。

float?p3x = 80838.0f;
float?p2y = -2499.0f;
double?v321 = p3x * p2y;
std::cout.precision(15);
std::cout?<< v321 << std::endl;

呃,好像x86、x64都是這個合理的結果 -202014160。奇了個怪了。其實上面這段C++代碼在不同的平臺下的結果如下:

  • Windows 32/64位下:-202014160

  • Linux 64位下(CentOS 6 gcc 4.4.7):-202014160

  • Linux 32位下(Ubuntu 12.04+ gcc 4.6.3)是:-202014162

補充說明:當初這篇文章投稿到酷殼,著名程序員左耳朵耗子那邊,這部分結果數據來自耗子叔對文章做的部分調整。(因為當初行文沒抓住重點,還引來了不少吐槽)

合理的運算結果,應該是-202014160,正確的運算結果是-202014162,合理性是浮點精度不夠造成的(后文解釋了合理性)。若是用兩個double相乘可得正確且合理的運算結果。// 就別糾結我用的“正確、合理”這兩個詞是否恰當了。問題是為何C#下X64和X86結果不一致?

浮點運算結果錯誤但合理的解釋

為何 ?80838.0f * -2499.0f = -202014160.0?是合理的?

32位浮點數在計算機中的表示方式為:1位符號位(s)-8位指數位(E)-23位有效數字(M),即:

其中E是實際轉換成1.xxxxx*2^E的指數,M是去掉 1 后的前面的xxxxx(節約1位)。

1. ?80838.0 如何表達?

80838.0?= 1 0011 1011 1100 0110.0(二進制) = 1.0011 1011 1100 0110 0*2^16

有效位M?=?0011?1011 1100?0110 0000 000(一共 23 位)

指數位E =?16 + 127 = 143 =?10001111

內部表示 80838.0 =?0?[10001111] [0011 1011 1100 0110 0000 000]?= 0100 0111 1001 1101 1110 0011 0000 0000?= 47 9d e3 00?//實際調試時看到的內存值 可能是00 e3 9d 47是因為調試環境用了小端表示法法:低位字節排內存低地址端,高位排內存高地址

2. -2499.0 如何表達?

-2499.0?= -100111000011.0 = -1.001110000110 * 2^11

有效位M =?0011 1000 0110 0000 0000 000

指數位E = 11+127=138=?10001010

符號位s = 1

內部表示-2499.0 = 1 [10001010] [0011 1000 0110 0000 0000 000]

=1100 0101 0001 1100 0011 0000 0000 0000?=c5 1c 30 00

3. 如何計算?80838.0 * -2499.0 = ?

指數?e?= 11+16 = 27

則指數位?E = e + 127 = 154 = 10011010

有效位相乘結果為 1.1000 0001 0100 1111 1011 1010 01?(可以自己動手實際算下),實際中只能有23位,后面的被截斷即1000 0001 0100 1111 1011 1010 01,相乘結果內部表示=1[10011010][1000 0001 0100 1111 1011 101]?= 1100 1101 0100 0000 1010 0111 1101 1101 =?cd?40?a7?dd

結果 = -1.1000 0001 0100 1111 1011 101 *2^27

= -11000 0001 0100 1111 1011 1010000

= -202014160

通過上面得知,32 位浮點數,-202014160?就是合理的結果,完全能解釋清楚。但如果有效數字更長的話, 上面的就不會被截斷。

4. 正確的結果-202014162怎么得來?

有效位相乘結果為 1.1000 0001 0100 1111 1011 1010 01

即結果 = -1.1000 0001 0100 1111 1011 101001?*2^27

= -11000 0001 0100 1111 1011 101001?=?-202014162

根因挖掘

上面部分解釋了兩種結果的來源,但貌似沒從根本回到為什么?用C++同樣的代碼,X86,X64(DEBUG下,這個后面會說)下得到一致的結果-202014160,容易理解且也是合理的。原因何在?看下編譯后生成的代碼(截取關鍵部分)

//C# x86 下
......
float p3x = 80838.0f;
0000003b mov dword ptr [ebp-40h],479DE300h
float p2y = -2499.0f;
00000042??mov dword ptr [ebp-44h],0C51C3000h
double v321 = p3x * p2y;
00000049??fld dword ptr [ebp-40h]
0000004c fmul dword ptr [ebp-44h]
0000004f??fstp qword ptr [ebp-4Ch]
.......

//C# X64下
......
float p3x = 80838.0f;
00000045??movss xmm0,dword ptr [00000098h]
0000004d??movss dword ptr [rbp+3Ch],xmm0
float p2y = -2499.0f;
00000052??movss xmm0,dword ptr [000000A0h]
0000005a movss dword ptr [rbp+38h],xmm0
double v321 = p3x * p2y;
0000005f??movss xmm0,dword ptr [rbp+38h]
00000064??mulss xmm0,dword ptr [rbp+3Ch]
00000069??cvtss2sd xmm0,xmm0
0000006d??movsd mmword ptr [rbp+30h],xmm0
......

C++ x86 / x64下都生成了類似的代碼(這也就是為何 C++ x86/x64與C#x64結果一致)即都用了先用浮點乘起來(mulss),然后轉成double(cvtss2sd)。從上面的匯編代碼可以看出 C# X86生成代碼用的指令fld/fmul/fstp等。其中fld/fmul/fstp等指令是由FPU(float point unit)浮點運算處理器做的,FPU在進行浮點運算時,用了80位的寄存器做相關浮點運算,然后再根據是float/double截取成32位或64位。非FPU的情況是用了SSE中128位寄存器(float實際只用了其中的32位,計算時也是以32位計算的),這就是導致上述問題產生的最終原因。

浮點運算標準IEEE-754 推薦標準實現者提供浮點可擴展精度格式(Extended precision),Intel x86處理器有FPU(float point unit)浮點運算處理器支持這種擴展。C#的浮點是支持該標準的,其中其官方文檔也提到了浮點運算可能會產生比返回類型更高精度的值(正如上面的返回值精度就超過了float的精度),并說明如果硬件支持可擴展浮點精度的話,那么所有的浮點運算都將用此精度進行以提高效率,舉個例子x*y/z, x*y的值可能都在double的能力范圍之外了,但真實情況可能除以z后又能把結果拉回到double范圍內,這樣的話,用了FPU的結果就會得到一個準確的double值,而非FPU的就是無窮大之類的了。

即產生如上的結果原因是,兩個浮點數相乘在非FPU的情況下,用了32位計算產生的結果導致結果存在誤差,而FPU是用了80位進行計算的,所以得到的結果是精度很高的,體現在本文的案例上就是個位數上的2。所以大家在寫代碼的時候得保證實際運行環境/測試環境/開發環境的一致性(包括OS架構啊、編譯選項等)啊,不然莫名其妙的問題會產生(本文就是開發環境與運行環境不一致導致的問題,糾結了好久才發現是這個原因);遇到涉及浮點運算的時候別忘了有可能是這個原因產生的;另外,float/double混用的情況得特別注意。

總結一下,本文通過分析之前遇到的一個疑難雜癥帶著大家一塊回顧或者學習了一下計算機內部浮點數的表達,解決了疑問。有時候可能需要跟進到硬件底層,當然隨著硬件技術的發展,可能以前理所當然的東西在新硬件的情況下也會有所不同(例如文中提到的 FPU 也有更高端的技術來替換了,本人

特別推薦一個分享架構+算法的優質內容,還沒關注的小伙伴,可以長按關注一下:

長按訂閱更多精彩▼

如有收獲,點個在看,誠摯感謝

創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

總結

以上是生活随笔為你收集整理的活动平台为何没有x86?_一个由跨平台产生的浮点数bug | 有你意想不到的结果的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。