考慮一下下面的的問題:
你有一列浮點類型的數字。這絕不是令人讨厭的惡作劇----沒有無窮個數字或無限大的數字,僅僅隻是正常的“簡單的”浮點型的數字。
現在:計算其平均值。你能做到嗎?
事實證明這是一個很困難的問題,想要得到該平
使用Hypothesis庫來考慮以下的測試案列:
這并不是關于正确性的測試,隻是測試平均值是否在列表的合理的限制範圍内:在不作為平均值的情況下,有許多函數可以滿足這個要求。最小值和最大值函數都滿足這個要求,中值函數也是如此。
然而,幾乎沒有人的平均值計算方法滿足這個要求。
為了理解其中的原因,寫下了我們自己的平均值計算方法:
這看起來十分合理--它正是平均值的定義--但是,它是錯誤的:
其問題在于有限的浮點數可能足夠大,以至于它們的和溢出到無窮大。然後當你用無窮大除以一個有限的數時,你仍然會得到無窮大,這就意味着超出了範圍。
所以,為了阻止有限的浮點數之和的溢出,我們嘗試通過列表的長度來限制我們的數字大小:
在這種情況下,你遇到的問題不是溢出,而是浮點數的精度不夠:浮點數隻能精确到一個整數的2次幂,因此除以3會導緻舍入錯誤。在這種情況下我們有一個問題,就是(x/3)* 3一般不等于x本身。
所以,現在我們理解了為什麼求平均值可能非常困難。讓我們看看現有的方法是如何滿足這個測試的。
首先,我們嘗試使用numpy庫:
這遇到了我們在第一次實驗中遇到的問題:
Python3.4還提供了新的統計模塊。糟糕的是,這個模塊也出現了問題(在Python3.5.2中得到了修複):
在之前溢出到無窮大的情況下,這反而會産生一個錯誤。該錯誤産生的原因是統計模塊在内部将所有數字都轉化成Fraction類型,這是一種任意精度的有理數類型。由于一些細節,即在何時何地被轉化為浮點數,這就産生了一個不容易被轉化為浮點數的有理數。
編寫一個通過測試的方法相對容易,僅僅需要簡單的作弊,而不需要實際計算出其平均值:
也就是說,将值限制在期望的範圍内。
然而,編寫一個真正地,正确的平均值計算方法(可以通過測試的)是相當困難的:
為了理解其困難程度,這裡有一篇30頁的關于計算兩個數的平均值的論文。
如果我是你,我就不會去看那篇論文。我已經閱讀過這篇論文了,但我并沒有記得很多細節。
這個測試是一個很好的實例:一旦你編寫的代碼沒有崩潰,測試工作正常進行,就可以開始在結果值上添加額外的約束。正如本例所示,即使你添加的約束非常寬松,它也常常會捕獲到一些有趣的bug。
它還證明了一個問題:浮點數運算非常困難,這使得它不太适合用Hypothesis庫進行驗證。
這并不是因為Hypothesis庫不擅長測試浮點代碼,而是因為它善于向人們展示編程的實際難度,而浮點編碼比人們所預想的要難得多。
因此,你或許并不會在意它将發現的一些bug:一般來說,大多數人對于浮點數錯誤的态度是,”那些數字好奇怪,我們并不真的在意它們。或許已經足夠好了“。如果你希望你的浮點代碼是正确的,那麼數值敏感度分析工作是必不可少的,但是很少有人能夠完成這種高要求的工作。
我過去經常用這個例子來向人們演示Hypothesis庫,但由于這些問題,我不會再這樣做了:告訴人們他們并不想要修複的bug,既不會修複bug,也不會得到朋友。
但是,值得知道的是,這是一個問題:編程是非常困難的,而忽略這些問題并不會使它變得容易。你可以忽略正确性問題,直到它們真正給你造成麻煩為止,但是當它們給你帶來麻煩時,最好不要感到驚訝。
而且,一些通用的技術也值得被牢記,因為這不僅僅是對浮點數有用:大多數的代碼可以從中受益,而且大多數時候它告訴你的bug不會那麼令人不快。
英文原文:https://hypothesis.works/articles/calculating-the-mean/ 譯者:Lyx,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!