code.club

 找回密碼
 立即註冊
搜索
查看: 8736|回復: 4
打印 上一主題 下一主題

有關浮點數相減時的問題

[複製鏈接]
跳轉到指定樓層
樓主
發表於 2018-4-21 18:39:35 | 只看該作者 回帖獎勵 |倒序瀏覽 |閱讀模式
本帖最後由 swift 於 2018-4-22 13:52 編輯

在寫計算機程式時,有一個困擾就是浮點數的減法會出現誤差。例如:
123.005 - 123.004 會得到 0.0009999...... 而非預期中的 0.001 。
因此寫了一個函式來解決這問題。
基本上是先求出二個數小數點後的位數,以最多的為準,乘上十的次方,再乘回二數將它們都變成整數,相減之後,得到的結果再除以剛才的十的次方,再變回字串出來。

  1. func subtractFloat(s1:String, s2:String) -> String { //輸入二個數字字串
  2.     var c:Double = 0 //用做計算結果之用
  3.     var k:Double = 1 //作為十的次方之用
  4.     let n1:Double! = Double(s1) //轉換字串為浮點數
  5.     let n2:Double! = Double(s2) //轉換字串為浮點數
  6.     var a1 = Array(s1) //轉換字串為字元陣列
  7.     var a2 = Array(s2) //轉換字串為字元陣列
  8.     var counter1 = 0 //第一個陣列的元素個數
  9.     var counter2 = 0 //第二個陣列的元素個數
  10.     var afterPoint = 0 //看誰的小數點後面的數字個數多就給它
  11.        
  12.         //求出二個數字小數點之後的數字個數
  13.     for i in 0...(a1.count-1) {
  14.         if a1[i] == "." {
  15.             counter1 = a1.count - i - 1
  16.         }
  17.     }
  18.     for i in 0...(a2.count-1) {
  19.         if a2[i] == "." {
  20.             counter2 = a2.count - i - 1
  21.         }
  22.     }
  23.        
  24.         //將比較多的個數給予afterPoint
  25.     if counter1 >= counter2 {
  26.         afterPoint = counter1 }
  27.     else {
  28.         afterPoint = counter2
  29.     }
  30.    
  31.         //將afterPoint當成十的次方數
  32.     for _ in 1...afterPoint {
  33.         k = k*10
  34.     }
  35.    
  36.        
  37.     c = (n1*k - n2*k)/k //先將二個數字乘上k都變成整數再相減,減完後再除以k回歸為浮點數
  38.    
  39.        
  40.     return String(c) //將結果轉換成字串回傳

  41. }
複製代碼

回復

使用道具 舉報

沙發
 樓主| 發表於 2018-4-22 15:23:51 | 只看該作者
本帖最後由 swift 於 2018-4-22 15:27 編輯
  1. //檢查小數點之後是否有數字
  2. func pointCheck(s:String) -> Bool {
  3.   let n = Double(s)
  4.     if Double(Int(n!)) != n {
  5.         return true
  6.     } else {
  7.         return false
  8.          }
  9. }
複製代碼
回復 支持 反對

使用道具 舉報

板凳
 樓主| 發表於 2018-4-22 15:43:08 | 只看該作者
本帖最後由 swift 於 2018-4-22 15:44 編輯

具體操作如下:

var x1 = "123.456"
var x2 = "33"
var x3 = "56"

if pointCheck(s:x1)||pointCheck(s:x2) { //如果任一數字中有小數點以後的數字,進入函數相減
    print(subtractFloat(s1:x1,s2:x2))
} else {
    print(String(Int(x1)!-Int(x2)!)) //如果沒有表示二者皆為整數,就直接相減
}
回復 支持 反對

使用道具 舉報

地板
 樓主| 發表於 2018-4-22 18:13:27 | 只看該作者
本帖最後由 swift 於 2018-4-23 02:49 編輯

最終解決函式:

  1. func minus(str1:String,str2:String){
  2.     if pointCheck(s:str1)||pointCheck(s:str2) {
  3.     print(subtractFloat(s1:str1,s2:str2))
  4. } else {
  5.     print( String(Int(Double(str1)!) - Int(Double(str2)!)) ) /*為何在此還要先把已知無小點的字串先換成Double再Int呢?因為如果字串是 "33.0"這種的,直接Int("33.0")會得到 nil 的答案,而造成程式fatal error。所以再多加一層轉換來把關*/
  6. }
  7. }
複製代碼


目前變數的 optional 尚未列入考慮。但如果是應用在iPhone上,由於設定了使用數字鍵,所以基本上不會出現非數字的字母。
回復 支持 反對

使用道具 舉報

5#
 樓主| 發表於 2018-8-11 20:42:14 | 只看該作者
本帖最後由 swift 於 2018-8-11 20:45 編輯

以上的式子證明依然有誤差。後來發現一個很簡單的解決方法,就是使用 Decimal()。如下:

func subtractFloat(s1:String, s2:String) -> String {
   
   let n1 = Decimal(string: s1)
   let n2 = Decimal(string: s2)
   return "\(n1! - n2!)"  //好像這是個 Decimal? 型別,所以都要加 !

}
回復 支持 反對

使用道具 舉報

您需要登錄後才可以回帖 登錄 | 立即註冊

本版積分規則

小黑屋|手機版|Archiver|code.club  

GMT+8, 2024-11-21 17:13 , Processed in 0.074897 second(s), 17 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回復 返回頂部 返回列表