一題計算分數加減表達式的值。加減表達式是1/1、1/2、1/3、1/4、1/5這種多分之一,又加又減的狀態,其實還是一個累加的變種,答案是要保留四位小數。
上一題是按照要求用整數去計算的,這個就投機取巧一下,直接給它寫成一個1.0/1、1.0/2的形式,先加后減,直接寫就行了。這是計算分數加減表達式的值,這些也就都不要了。
需要從1/1到n分之一,所以four循環,int i等于1,i小于等于n,i加用來表示1.0除以這個分數,然后用ws等于0,累加初始化s加等于它。這樣一來就能保證這個數字是1/1加1/2加1/3加1/4的狀態,求的是1/1減1/2加1/3減1/4,求的是這個。
所以得知道它的前面是正號還是負號,假設它是正號,那么就可以讓它等于1S,每次加的時候用1乘以它,符號還是正號,計算一次,用它乘以-1就變成負數了,負數再乘以-1就變成正數了,再乘以-1又變成負數了,以此就可以加入加減的運算。
最后輸出s,保留小數,應該是4位,如果沒記錯,四位提交。今天這個通過還算很順利。
4位浮點型數據運算
在這周網絡授課講解變量與常量案例章節中,在計算0.1+0.2時,出現的結果與預期不符合,對此問題很多學生較為糾結。為解決這個問題,本節課主要講授JavaScript數值型數據存儲格式及浮點型數據的加運算操作,以解決各位同學的困惑。
例題:請編寫JS程序計算0.1+0.2,并通過控制臺輸出計算結果。
浮點數計算問題
該程序直觀判斷可知計算結果為0.3,但是通過控制臺輸出顯示的結果卻不是0.3,其計算結果為:0.30000000000000004。這一問題也是在浮點類型數據相加操作中代表性問題之一。產生這一問題的原因在于浮點類型數據的存儲與運算規則、過程。以下對問題進行解釋說明。
浮點數加操作問題
JavaScript程序設計語言存儲數值型數據與其他程序設計語言相比較,沒有單獨對整數、小數進行精確劃分,在實際存儲過程中將所有數值按照IEEE754標準使用64位浮點數存儲數值。64位浮點類型也稱為雙精度浮點類型,占用8個字節,共64位。存儲結構如下圖所示:
64位浮點類型數據結構
在該結構中,64位共分為三組,最高位為符號位用于表示數值的正負,即上圖藍色部分,綠色部分11位用于存儲指數值(浮點類型表示類似于科學計數法),剩余52位用于存儲小數位即有效數字。例如:0.1 的64位浮點表示值為:
0011111110111001100110011001100110011001100110011001100110011010
本例所提出的問題需要計算0.1與0.2的和,針對JavaScript腳本語言首先需要將其轉換為64位浮點類型。浮點類型數據的表現形式及說明如下:
浮點類型數值形式
其中s符號位占1位,指數E占11位,有效數字占52位。其M值可通過左移右移實現在1~2范圍之內。如計算M值為101*2^2可表示為1.01*2^4。對于各部分IEEE754給出了明確的定義,其定義描述如下:
64位浮點數描述
在執行過程中首先需要將十進制小數轉換為2進制表示形式,針對題目操作數0.1余0.2,他們對應二進制表示形式如下:
0.2十進制:0.00110011(下劃線部分表示無限循環)
0.1十進制: 0.000110011(下劃線部分表示無限循環)
兩位小數的二進制表示描述如上,如實際存儲過程中需要指定長度的話直接用循環部分填充即可。以計算出的二進制為基礎可以求出對應的s、M、E分別為多少,其中10進制的0.2求解結果如下:
64位浮點存儲求解
以上為基礎我們可以求出兩位操作數的64位存儲相關參數。求解結果描述如下圖所示:
64位浮點表示數據
64位浮點類型數據在進行加法運算時需要按照其運算規則執行運算,其運算規則描述如下:
浮點數的加法操作過程
1.對階
主要是指將兩個操作數的指數部分調整為一樣,本例題指數部分分別為-3,-4,按照對階要求向大的靠攏,需要將-4指數調整為-3。在對階過程需要對M部分作出對應調整。調整結果描述如下:
對階處理
經過對階處理之后所有的指數都變為01111111100。因此我們可以進一步對M部分進行加法操作。
2.M尾數運算
尾數運算部分主要按照二進制數值進行求和運算即可。在計算過程中可能因為進位關系導致數據整體長度發生變化即產生溢出。本例尾數部分運算過程與結果描述如下:
尾數求和
3.規格化處理(右規)
本例運算過程由于加運算產生進位而導致溢出,最終計算結果形式為10.x x x … x。這種形式需要通過對其右規實現規格化處理。尾數每右移一位,階碼相應加 1,最高位補0。
規格化處理
4.舍入處理
在規格化過程中隨著右移操作,最右側位將會被丟掉,因此需要通過舍入處理減少因丟棄導致的精度損失。本例題采用就近舍入方法,最后舍棄位為1,舍棄之后在剩余結果最低位加1,因此最后四位變為0100。操作結果描述如下:
舍棄處理操作
綜上所述,本例題最終0.1加0.2的計算結果二進制表示為:
01.0011001100110011001100110011001100110011001100110100*2^-2
在獲取二進制計算結果之后,為方便觀察,我們將該二進制數據恢復為10進制數據,然后進行結果判斷,首先去掉指數部分結果如下:
0.010011001100110011001100110011001100110011001100110100
轉換結果為:
0.30000000000000004441
如上所示,通過實際手工計算浮點型數據加運算,我們可以清楚了解在JavaScript進行0.1與0.2加運算時出現0.30000000000000004的原因了。如有問題不清楚同學可在評論區留言評論,如發現錯誤也可留言,大家共同探討、學習、提高。
本頭條號長期關注編程資訊分享;編程課程、素材、代碼分享及編程培訓。如果您對以上方面有興趣或代碼錯誤、建議與意見,可以聯系作者,共同探討。更多程序設計相關教程及實例分享,期待大家關注與閱讀!系列教程鏈接如下:
JavaScript基礎教程(二)變量、常量與運算符
JavaScript基礎教程(一)課程說明
S 加法器模擬,實現
位運算 & 簡單的 assert 斷言
// 常規位運算 // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators const AND = (a, b) => a & b; const OR = (a, b) => a | b; const XOR = (a, b) => a ^ b; const NOT = a => ~a; // Fake Node Assert Lib const assert = { deepEqual: (a, b) => { if (a.toString() === b.toString()) return; throw new Error(`Not Equal: ${a} ${b}`); } }
/** * 半加器 * 兩個 bit 輸入,輸出數組 [進位,和] * 如: * 1,1 => [1, 0] * @param {bit} a 輸入 * @param {bit} b 輸入 */ const HalfAdder = (a, b) => [a & b, a ^ b]; // 半加器測試 assert.deepEqual(HalfAdder(0, 0), [0, 0]); assert.deepEqual(HalfAdder(0, 1), [0, 1]); assert.deepEqual(HalfAdder(1, 0), [0, 1]); assert.deepEqual(HalfAdder(1, 1), [1, 0]);
/** * 全加器 * 兩個 bit 輸入,和進位輸入,輸出數組 [進位,和] * 如: * 0,1,1 => [1, 0] * @param {bit} a 輸入 * @param {bit} b 輸入 * @param {bit} c 進位輸入 */ const FullAdder = (a, b, c) => { var t1 = HalfAdder(a, b); var t2 = HalfAdder(t1[1], c); return [t1[0] | t2[0], t2[1]]; } // 全加器測試 assert.deepEqual(FullAdder(0, 0, 0), [0, 0]); assert.deepEqual(FullAdder(1, 0, 0), [0, 1]); assert.deepEqual(FullAdder(0, 1, 0), [0, 1]); assert.deepEqual(FullAdder(0, 0, 1), [0, 1]); assert.deepEqual(FullAdder(1, 1, 0), [1, 0]); assert.deepEqual(FullAdder(1, 0, 1), [1, 0]); assert.deepEqual(FullAdder(0, 1, 1), [1, 0]); assert.deepEqual(FullAdder(1, 1, 1), [1, 1]);
/** * 波紋加法器, 4 位加法器 * 如: * [0, 1, 0, 1],[0, 1, 0, 1] => [1, 0, 1, 0] * @param {Array<Number>} a 4位 bit 輸入數組,如:[0, 1, 0, 1] * @param {Array<Number>} b 4位 bit 輸入數組,如:[0, 1, 0, 1] * @returns {Array<Number>} */ const RippleCarryAdder = (a, b) => { let carry = 0; let bit = 3; let result = []; while(bit >= 0) { let temp = FullAdder(a[bit], b[bit], carry); carry = temp[0]; result.push(temp[1]); bit--; } return result.reverse(); } /** * 將數字轉成 4 位二進制數組 * 如: * 1 => [0, 0, 0, 1] * 3 => [0, 0, 1, 1] * @param {Number} a 數字 * @returns {Array<Number>} */ const to4Bit = a => ( a.toString(2) .split('') .reverse() .concat(Array(4).fill('0')) .slice(0,4) .reverse() .map(i => +i) ); /** * 將二進制字符串轉為數字 * 如: * '1010' => 10 * @param {String} a 4 位二進制字符串 * @returns {Number} */ const from4Bit = a => parseInt(a, 2); /** * 加法簡寫工具 * @param {Number} a 輸入 * @param {Number} b 輸入 */ const helper = (a, b) => ( from4Bit(RippleCarryAdder( to4Bit(a), to4Bit(b) ).join('')) ) assert.deepEqual(helper(0, 0), 0); assert.deepEqual(helper(1, 1), 2); assert.deepEqual(helper(1, 2), 3); assert.deepEqual(helper(2, 2), 4); assert.deepEqual(helper(3, 5), 8); assert.deepEqual(helper(1, 14), 15); // 9 + 14 為 23,但由于我們寫的是 4 位加法器,所以有溢出 // 最終的結果需要 mod 0x10(也就是 16) assert.deepEqual(helper(9, 14), 23 % 0x10); assert.deepEqual(helper(9, 14), 7);
我們以 4 bit 存儲數字,并以最高位作為符號位
*請認真填寫需求信息,我們會在24小時內與您取得聯系。