為什麼你需要少看中文技術博客以及如何在Python裡面精確四捨五入

2021-02-19 未聞Code

今天又有一個Python初學者被中文技術博客中的垃圾文章給誤導了。

這位初學者的問題是:

在Python中,如何精確地進行浮點數的四捨五入,保留兩位小數?

如果你在Google或者百度上搜索,你會發現大量的來自CSDN或者簡書上面的文章講到這一點,但是他們的說法無外乎下面幾種:

連例子都不舉的垃圾文章

如下圖所示,懶得吐槽。

使用round函數

他們舉的例子為:

>>> round(1.234, 2)

1.23

這種文章,他只演示了 四舍,但是卻沒有演示 五入。所以如果你代碼稍作修改,就會發現有問題:

>>> round(11.245, 2)

11.24

先放大再縮小

這種文章稍微好一點,知道多舉幾個例子:

然而這種文章也是漏洞百出,只要你多嘗試幾個數字就會發現問題,在Python 2和Python 3下面,效果是不一樣的。先來看看Python 2下面的運行效果:

在Python 2裡面,直接使用 round, 1.125精確到兩位小數後為 1.13,而 1.115精確到兩位小數後是 1.11。

再來看看Python 3下面的效果:

在Python 3下面, 1.125在精確到兩位小數以後是 1.12。

他舉的例子,在Python 3中先放大再縮小,也並不總是正確。

裝逼貨

還有一種裝逼貨,文章和先放大再縮小差不多,但是他還知道 decimal這個模塊。

不過他的使用方法,大家看他吧

具體原因不詳 ????

不推薦使用這個方法???

這種人要先裝個逼,表示自己知道有這樣一個庫,但是用起來發現有問題,而且不知道原因,所以不建議大家使用。

decimal是專門為高精度計算用的模塊,他竟然說不建議大家使用???

round到底出了什麼問題?

罵完了,我們來說說,在Python 3裡面, round這個內置的函數到底有什麼問題。

網上有人說,因為在計算機裡面,小數是不精確的,例如 1.115在計算機中實際上是 1.1149999999999999911182,所以當你對這個小數精確到小數點後兩位的時候,實際上小數點後第三位是 4,所以四捨五入,因此結果為 1.11。

這種說法,對了一半。

因為並不是所有的小數在計算機中都是不精確的。例如 0.125這個小數在計算機中就是精確的,它就是 0.125,沒有省略後面的值,沒有近似,它確確實實就是 0.125。

但是如果我們在Python中把 0.125精確到小數點後兩位,那麼它的就會變成 0.12:

>>> round(0.125, 2)

0.12

為什麼在這裡 四舍了?

還有更奇怪的,另一個在計算機裡面能夠精確表示的小數 0.375,我們來看看精確到小數點後兩位是多少:

>>> round(0.375, 2)

0.38

為什麼這裡又 五入了?

因為在Python 3裡面, round對小數的精確度採用了 四捨六入五成雙的方式。

如果你寫過大學物理的實驗報告,那麼你應該會記得老師講過,直接使用四捨五入,最後的結果可能會偏高。所以需要使用 奇進偶舍的處理方法。

例如對於一個小數 a.bcd,需要精確到小數點後兩位,那麼就要看小數點後第三位:

如果 d小於5,直接捨去

如果 d大於5,直接進位

如果 d等於5:

d後面沒有數據,且c為 偶數,那麼不進位,保留c

d後面沒有數據,且c為 奇數,那麼進位,c變成(c + 1)

如果 d後面還有非0數字,例如實際上小數為 a.bcdef,此時一定要進位,c變成(c + 1)

關於奇進偶舍,有興趣的同學可以在維基百科搜索這兩個詞條: 數值修約和 奇進偶舍。

所以, round給出的結果如果與你設想的不一樣,那麼你需要考慮兩個原因:

你的這個小數在計算機中能不能被精確儲存?如果不能,那麼它可能並沒有達到四捨五入的標準,例如 1.115,它的小數點後第三位實際上是 4,當然會被捨去。

如果你的這個小數在計算機中能被精確表示,那麼, round採用的進位機制是 奇進偶舍,所以這取決於你要保留的那一位,它是奇數還是偶數,以及它的下一位後面還有沒有數據。

如何正確進行四捨五入

如果要實現我們數學上的四捨五入,那麼就需要使用decimal模塊。

如何正確使用decimal模塊呢?

看官方文檔,不要看中文垃圾博客!!!

看官方文檔,不要看中文垃圾博客!!!

看官方文檔,不要看中文垃圾博客!!!

不要擔心看不懂英文,Python已經推出了官方中文文檔(有些函數的使用方法還沒有翻譯完成)。

我們來看一下:https://docs.python.org/zh-cn/3/library/decimal.html#decimal.Decimal.quantize

官方文檔給出了具體的寫法:

>>>Decimal('1.41421356').quantize(Decimal('1.000'))

Decimal('1.414')

那麼我們來測試一下, 0.125和 0.375分別保留兩位小數是多少:

>>> from decimal import Decimal

>>> Decimal('0.125').quantize(Decimal('0.00'))

Decimal('0.12')

>>> Decimal('0.375').quantize(Decimal('0.00'))

Decimal('0.38')

怎麼結果和 round一樣?我們來看看文檔中 quantize的函數原型和文檔說明:

這裡提到了可以通過指定 rounding參數來確定進位方式。如果沒有指定 rounding參數,那麼默認使用上下文提供的進位方式。

現在我們來查看一下默認上下文中的進位方式是什麼:

>>> from decimal import getcontext

>>> getcontext().rounding

'ROUND_HALF_EVEN'

如下圖所示:

ROUND_HALF_EVEN實際上就是 奇進偶舍!如果要指定真正的四捨五入,那麼我們需要在 quantize中指定進位方式為 ROUND_HALF_UP:

>>> from decimal import Decimal, ROUND_HALF_UP

>>> Decimal('0.375').quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)

Decimal('0.38')

>>> Decimal('0.125').quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)

Decimal('0.13')

現在看起來一切都正常了。

那麼會不會有人進一步追問一下,如果Decimal接收的參數不是字符串,而是浮點數會怎麼樣呢?

來實驗一下:

>>> Decimal(0.375).quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)

Decimal('0.38')

>>> Decimal(0.125).quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)

Decimal('0.13')

那是不是說明,在Decimal的第一個參數,可以直接傳浮點數呢?

我們換一個數來測試一下:

>>> Decimal(11.245).quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)

Decimal('11.24')

>>> Decimal('11.245').quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)

Decimal('11.25')

為什麼浮點數 11.245和字符串 '11.245',傳進去以後,結果不一樣?

我們繼續在文檔在尋找答案。

官方文檔已經很清楚地說明了,如果你傳入的參數為浮點數,並且這個浮點值在計算機裡面不能被精確存儲,那麼它會先被轉換為一個不精確的二進位值,然後再把這個不精確的二進位值轉換為 等效的十進位值。

對於不能精確表示的小數,當你傳入的時候,Python在拿到這個數前,這個數就已經被轉成了一個不精確的數了。所以你雖然參數傳入的是 11.245,但是Python拿到的實際上是 11.244999999999...。

但是如果你傳入的是字符串 '11.245',那麼Python拿到它的時候,就能知道這是 11.245,不會提前被轉換為一個不精確的值,所以,建議給 Decimal的第一個參數傳入字符串型的浮點數,而不是直接寫浮點數。

總結,如果想實現精確的四捨五入,代碼應該這樣寫:

from decimal import Decimal, ROUND_HALF_UP

origin_num = Decimal('11.245')

answer_num = origin_num.quantize(Decimal('0.00'), rounding=ROUND_HALF_UP)

print(answer_num)

運行效果如下圖所示:

特別注意,一旦要做精確計算,那麼就不應該再單獨使用浮點數,而是應該總是使用 Decimal('浮點數')。否則,當你賦值的時候,精度已經被丟失了,建議全程使用Decimal舉例:

a = Decimal('0.1')

b = Decimal('0.2')

c = a + b

print(c)

總之,你遇到的絕大部分問題,在官方文檔裡面都能找到原話,多到文檔中尋找答案,少看裝逼貨的垃圾博客。

最後,如果有同學想知道為什麼0.125和0.375能被精確的儲存,而1.115、11.245不能被精確儲存,請在這篇文章下面留言,如果想知道的同學多,我就寫一篇文章來說明。

相關焦點

  • 如何在 Python 裡面精確四捨五入?
    那 Python 是如何處理浮點數的四捨五入問題的呢?今天分享的文章,對此展開了深入的剖析。本文作者 kingname,來自網易,公眾號「未聞Code」。如需轉載,請聯繫作者。今天又有一個Python初學者被中文技術博客中的垃圾文章給誤導了。這位初學者的問題是:在Python中,如何精確地進行浮點數的四捨五入,保留兩位小數?
  • 關於Python的四捨五入詳解(確定你真的會四捨五入?)
    提出問題   1.在實際應用python解決問題中我們會需要用到round()函數,可是round()的確在遇到一些特殊問題的時候出現了弊端,我們應該如何去解決呢?    2.如何利用Python實現四捨五入。1、round()函數簡介正如菜鳥教程中說到,round() 方法返回浮點數x的四捨五入值。如圖所示,參數x,n均為數值表達式,返回值為x的四捨五入值。n為保留的小數位數,不加n則只保留x四捨五入後的整數部分。
  • 你會用英語說「四捨五入」嗎?
    聰明的你是否曾經注意過在使用 Excel 時,有時為了方便就需要對數字進行「四捨五入」老師會教你利用【ROUND()函數】「四捨五入」,但你知道它如何用英語表達嗎?中文的四捨五入是說,小於五的砍丟,大於等於五的就進位。而英文的卻用單詞「round」,指圍繞著,在周圍,取中心,取整。二者都很有畫面感,但是畫面卻完全不一樣哦。看到這裡你可能會覺得疑惑、不解:「round」在英文裡不就是圓形的,圍繞的意思麼?為什麼會是四捨五入呢?」
  • 計程車計費「四捨五入」,乘客據實支付有何不可?你怎麼看?
    此次漲價還不僅是起步價格的調整,起步價、單價都多了很大的調整,尤其是計價方式也較之前計價方式細緻了許多,以前都是整數計費,精確到個位。現在則是精確到以分為計價單位。也就是說以前打車兜裡面不需要準備零角的,現在看計價器計費方式可能是需要把上世紀八九十年代較為常見的分幣找出來才能夠滿足「找零」的需求。
  • 「四捨五入」你知道它如何用英語表達嗎?
    中文的四捨五入是說,小於五的砍丟,大於等於五的就進位。而英文的卻用單詞「round」,指圍繞著,在周圍,取中心,取整。
  • 生活的「四捨五入」
    比如買蘋果的時候,應該給9.40元,我會自動「四捨五入」,給賣家9元。工作中,我也很愛計較到小數點以後的數字。我是教師,教學成績是評價我們工作的重要標準。每次考試前,我都明裡暗裡跟同事較勁,希望能夠超過他們。有次期末考試,我的教學成績比同事高出0.8分,我得意地跟朋友說:「我的成績是第一名,比別人高了1分!」多年來,我一直在做著這樣的計算。
  • Excel如何把小數點四捨五入取整?
    在Excel表格中整理數據的時候,為了避免大量的小數點,通常都會採用四捨五入的方式,減少小數點,這樣工作起來也比較方便,還能提高工作效率。Excel如何把小數點四捨五入取整呢?下面一起看下解決的方法。操作步驟如下:方法一:四捨五入函數ROUND函數格式:ROUND(數值或數值單元格,要保留的位數)例:ROUND(20.156,2) 表示對20.156四捨五入,保留2位小數,結果為20.16
  • ETC收費「四捨五入」精確到分 江西高速公路通行費計費規則即將調整
    中國江西網/江西頭條新聞客戶端訊 記者陳雪紅報導:27日,記者從江西省高速集團獲悉,為適應深化收費公路制度改革取消高速公路省界收費站工作新形勢,根據省交通運輸廳、省發展改革委、省財政廳《關於調整我省高速公路計費規則有關事項的通知》(贛交財務字〔2019〕52號)文件精神,本月底,江西高速公路通行費的計費取整規則將作如下調整:  現金收費由目前「二舍三進
  • 新京報評論:別拿計程車「四捨五入」不當亂收費
    據紅星新聞報導,如今,行動支付讓部分行業因零錢難找而形成的「四捨五入」行規成了過去式,但近日有四川內江網友反映,用手機支付計程車費用時,遭遇零頭「四捨五入」行規,甚至4角零錢也未被「舍」掉,而被「入」為5角。
  • 專業英語—英文中的「四捨五入」怎麼說
    四捨五入是一種精確度的計數保留法,意即把小數點後面的數字四捨五入,英文的表達是round up, up有時可以省略, 直接說round to。Round up:To increase an exact figure to the next highest whole number 把數向上取整數,比如:round up to the nearest integer 四捨五入到最接近的整數rounding up days or months to the nearest year 將日期或月份四捨五入到最接近的年份
  • 你確定你真的學了Python?
    不就是0.1嘛」但是如果你用 python 去執行一下,會發現結果跟你想的不太一樣,如下圖:>>> 1.1 - 10.10000000000000009這樣大家是不是發現了什麼問題?是的,浮點數在運算過程中並沒有保證完全精確,是什麼原因導致了這種現象呢?
  • 長春某些飯店的四捨五入「抹零」法,你遇見過嗎?
    「這些東西一共五塊五。」「五毛錢就抹掉了吧,五塊行不?」「行,零錢就給你抹掉了吧。」生活中顧客經常這樣和菜攤老闆討價還價,大家也覺得這是很正常的事。可是,總有一些意外讓大家猝不及防,甚至大家都還不知道自己結帳時是咋被抹零的。大家要是不信的話,就拿出之前的購物小票看一眼,都發現了啥?
  • 學習四捨五入最簡單的方法?
    無論是解決數學問題還是會計,我們每天都在處理數字,為了方便起見,我們需要將這些數字四捨五入到最接近的可能值。將368捨入到最接近的百位數將是400。現在,問題是如何做到這一點?閱讀本文,您將了解可能的解決方案。
  • 五年級數學:說說小數的數位圖和「四捨五入」
    初學小數,要明白小數的意義是什麼,牢記小數的性質(是「小數的末尾」添上或去掉零,小數大小不變,而非「小數點後面」添上或去掉零,小數大小不變);由於我們的書寫習慣是從左往右,剛好小數的數位也是左邊高,右邊低,所以小數的讀寫都是從高位開始;學會了這些,才能進一步學習小數的比較大小、化簡、求近似值,以及之後的小數的加減乘除
  • 不要讓四捨五入虧了一方
    四捨五入是一種近似精確的計算方法,在Java 5之前,我們一般是通過使用Math.round來獲得指定精度的整數或小數的,這種方法使用非常廣泛,代碼如下:public class Client {       public static void main(String[] args) {            System.out.println("10.5
  • 天津一姐姐遇到這種「四捨五入」的商家……不差錢,差事兒!你怎麼看?
    昨天,一位網友就向新報記者反映,在金緯路一家店買元宵,總共消費了157.5元,當時並沒有看購物小票,結帳後,回到家裡才發現小票上結帳金額是158元,小票上還堂而皇之地註明:四捨五入0.5元。「這錢雖然不多,也不差那幾毛錢,但是沒經過我的同意就直接四捨五入收費,不是事兒……」這位網友說。
  • 電子秤「四捨五入」教程,掙多少錢就看你會不會算
    作為日常交易的計量工具,總有些不良的商家動些歪心思,在電子秤上動手腳來獲取大的利益;缺斤少兩的事件屢見不鮮,今天就來說說電子秤「四捨五入」的故事。1000克豬肉少80克市民林先生到菜市場買菜,來到豬肉攤,買了一公斤豬肉,商販切了一塊,看了看秤,差點便到兩斤,於是又隨手切了一小塊丟進塑膠袋,在電子秤上按了一下,隨即商販說道:「不用稱了,多點就多點吧。」林先生沒看到秤,於是就表示還是稱一下,結果還真是多了一點,這下林先生放心了。
  • 飯店想當然四捨五入多收了五毛 最後賠了188元
    浙江在線01月08日訊吃飯結帳,很少有人去看小數點後面的數字。杭州的鄧女士比較認真,前幾天和一個五毛錢的零頭幹上了。  那天,鄧女士去杭州百井坊巷的新庭記吃飯。結帳時,收銀員說388元。她付了錢,再拿小票核對。鄧女士說,這是自己的習慣,就像點魚時她會去廚房核對魚頭的大小。然後她很快發現:小票上的金額是387.5元。
  • ROUND(四捨五入)計算指定位數四捨五入某個數字
    ROUND計算指定位數四捨五入某個數字大家好,這節課我們來學習一下Round函數。ROUND,這個函數對於要計算錢的工作崗位尤其重要,人資計算稅金,財務計算工資,經常都會碰到小數點後幾位的數字,一般的做法就是直接在格式裡修改為保留2位小數。但是這個做法會有一個很大的隱患。
  • 付款被多收2分錢市民質疑超市「四捨五入」 監管部門:如實結算
    行動支付時代,人們買東西付錢,用現金的越來越少,都習慣於拿手機掃碼支付,無接觸式付款。近日,網友黃女士在長江網武漢城市留言板留言稱,在超市購物,掃碼結算時,超市方面「四捨五入」,多收了她2分錢:「若是使用現金支付,超市缺少分幣『四捨五入』可以理解,可行動支付為何也要『四捨五入』呢?」