Python 分數(shù)表示有理數(shù)(用友)

      網(wǎng)友投稿 947 2022-05-30

      目錄

      十進制與分數(shù)表示法

      數(shù)字分類

      數(shù)字系統(tǒng)和符號

      Python 中的數(shù)字數(shù)據(jù)類型

      從不同的數(shù)據(jù)類型創(chuàng)建 Python 分數(shù)

      有理數(shù)

      浮點數(shù)和十進制數(shù)

      字符串

      檢查 Python 分數(shù)

      將 Python 分數(shù)轉(zhuǎn)換為其他數(shù)據(jù)類型

      浮點數(shù)和整數(shù)

      十進制數(shù)

      字符串

      對分數(shù)執(zhí)行有理數(shù)算術(shù)

      添加

      減法

      乘法

      分配

      求冪

      舍入 Python 分數(shù)

      在 Python 中比較分數(shù)

      在分數(shù)、小數(shù)和浮點數(shù)之間進行選擇

      二進制浮點數(shù):float

      十進制浮點和定點:十進制

      無限精度有理數(shù):分數(shù)

      在行動中研究 Python 分數(shù)

      逼近無理數(shù)

      獲取顯示器的縱橫比

      計算照片的曝光值

      用 Python 分數(shù)表示有理數(shù)(用友)

      解決變革問題

      生產(chǎn)和擴大連續(xù)餾分

      結(jié)論

      fractionsPython 中的模塊可以說是標準庫中最未被充分利用的元素之一。盡管它可能并不廣為人知,但它是一個有用的工具,因為它可以幫助解決二進制浮點運算的缺點。如果您計劃處理財務(wù)數(shù)據(jù)或者您的計算需要無限精度,這非常重要。

      在本教程即將結(jié)束時,您將看到一些動手示例,其中分數(shù)是最合適和最優(yōu)雅的選擇。您還將了解他們的弱點以及如何在此過程中充分利用它們。

      在本教程中,您將學(xué)習(xí)如何:

      在十進制和小數(shù)表示法之間轉(zhuǎn)換

      執(zhí)行有理數(shù)算術(shù)

      近似無理數(shù)

      以無限精度精確表示分數(shù)

      知道什么時候選擇?Fraction了Decimal或float

      本教程的大部分內(nèi)容都介紹了該fractions模塊,除了了解其數(shù)字類型外,該模塊本身不需要深入的 Python 知識。但是,如果您熟悉更高級的概念(例如 Python 的內(nèi)置collections模塊、itertools模塊和生成器),那么您將處于一個很好的位置來完成后面的所有代碼示例。如果您想充分利用本教程,您應(yīng)該已經(jīng)熟悉這些主題。

      fractionsPython 中的模塊可以說是標準庫中最未被充分利用的元素之一。盡管它可能并不廣為人知,但它是一個有用的工具,因為它可以幫助解決二進制浮點運算的缺點。如果您計劃處理財務(wù)數(shù)據(jù)或者您的計算需要無限精度,這非常重要。

      在本教程即將結(jié)束時,您將看到一些動手示例,其中分數(shù)是最合適和最優(yōu)雅的選擇。您還將了解他們的弱點以及如何在此過程中充分利用它們。

      在本教程中,您將學(xué)習(xí)如何:

      在十進制和小數(shù)表示法之間轉(zhuǎn)換

      執(zhí)行有理數(shù)算術(shù)

      近似無理數(shù)

      以無限精度精確表示分數(shù)

      知道什么時候選擇?Fraction了Decimal或float

      本教程的大部分內(nèi)容都介紹了該fractions模塊,除了了解其數(shù)字類型外,該模塊本身不需要深入的 Python 知識。但是,如果您熟悉更高級的概念(例如 Python 的內(nèi)置collections模塊、itertools模塊和生成器),那么您將處于一個很好的位置來完成后面的所有代碼示例。如果您想充分利用本教程,您應(yīng)該已經(jīng)熟悉這些主題。

      十進制與分數(shù)表示法

      讓我們沿著記憶之路走一走,帶回您對數(shù)字的學(xué)校知識并避免可能的混淆。這里有四個概念在起作用:

      數(shù)學(xué)中的數(shù)字類型

      數(shù)制

      數(shù)字符號

      Python 中的數(shù)字數(shù)據(jù)類型

      您現(xiàn)在將快速了解其中的每一個,以更好地理解FractionPython 中數(shù)據(jù)類型的用途。

      數(shù)字分類

      如果您不記得數(shù)字的分類,這里有一個快速復(fù)習(xí):

      數(shù)字類型

      數(shù)學(xué)中有更多類型的數(shù)字,但這些是日常生活中最相關(guān)的數(shù)字。在最高層,你會發(fā)現(xiàn)復(fù)數(shù)包括虛和實數(shù)。反過來,實數(shù)又由有理數(shù)和無理數(shù)組成。最后,有理數(shù)包含整數(shù)和自然數(shù)。

      數(shù)字系統(tǒng)和符號

      幾個世紀以來,出現(xiàn)了各種視覺表達數(shù)字的系統(tǒng)。今天,大多數(shù)人使用基于印度-阿拉伯符號的位置數(shù)字系統(tǒng)。您可以為此類系統(tǒng)選擇任何基數(shù)或基數(shù)。然而,雖然人們更喜歡十進制系統(tǒng)(基數(shù)為 10),但計算機在二進制系統(tǒng)(基數(shù)為 2)中工作得最好。

      在十進制系統(tǒng)本身中,您可以使用替代符號表示一些數(shù)字:

      十進制:?0.75

      分數(shù):??

      這些都不比另一個更好或更精確。用十進制表示數(shù)字可能更直觀,因為它類似于百分比。比較小數(shù)也更直接,因為它們已經(jīng)有一個共同的分母——系統(tǒng)的基數(shù)。最后,十進制數(shù)可以通過保留尾隨零和前導(dǎo)零來傳達精度。

      另一方面,分數(shù)在手動執(zhí)行符號代數(shù)時更方便,這就是它們主要用于學(xué)校的原因。但是您還記得上次使用分數(shù)是什么時候嗎?如果你不能,那是因為十進制表示法是當今計算器和計算機的核心。

      分數(shù)符號通常僅與有理數(shù)相關(guān)聯(lián)。畢竟,有理數(shù)的定義表明,只要分母不為零,您就可以將其表示為兩個整數(shù)的商或分數(shù)。但是,當您考慮可以近似無理數(shù)的無限連分數(shù)時,這并不是全部:

      無理數(shù)總是有一個非終止和非重復(fù)的十進制擴展。例如,pi (π) 的十進制展開式永遠不會用完似乎具有隨機分布的數(shù)字。如果您要繪制它們的直方圖,那么每個數(shù)字的頻率將大致相似。

      另一方面,大多數(shù)有理數(shù)都有終止的十進制擴展。但是,有些可以無限循環(huán)十進制擴展,在一段時間內(nèi)重復(fù)一位或多位數(shù)字。重復(fù)的數(shù)字通常用十進制表示法中的省略號 (0.33333...) 表示。不管它們的十進制擴展如何,有理數(shù)(例如代表三分之一的數(shù)字)在分數(shù)表示法中總是看起來優(yōu)雅而緊湊。

      Python 中的數(shù)字數(shù)據(jù)類型

      具有無限十進制擴展的數(shù)字在作為浮點數(shù)據(jù)類型存儲在計算機內(nèi)存中時會導(dǎo)致舍入錯誤,而計算機內(nèi)存本身是有限的。更糟糕的是,通常不可能用二進制終止十進制擴展來精確表示數(shù)字!

      這被稱為浮點表示錯誤,它會影響所有編程語言,包括 Python。每個程序員遲早都會面臨這個問題。例如,您不能float在銀行等應(yīng)用程序中使用,在這些應(yīng)用程序中,必須在不損失任何精度的情況下存儲和處理數(shù)字。

      PythonFraction是解決這些障礙的方法之一。雖然它代表一個有理數(shù),但這個名字Rational已經(jīng)代表了numbers模塊中的一個抽象基類。該numbers模塊定義了抽象數(shù)字數(shù)據(jù)類型的層次結(jié)構(gòu),以對數(shù)學(xué)中的數(shù)字分類進行建模:

      Python 中數(shù)字的類型層次結(jié)構(gòu)

      Fraction是 的直接且具體的子類Rational,它提供了 Python 中對有理數(shù)的完整實現(xiàn)。積分類型像int和bool也派生自Rational,但那些更具體。

      有了這個理論背景,是時候創(chuàng)建你的第一個分數(shù)了!

      從不同的數(shù)據(jù)類型創(chuàng)建 Python 分數(shù)

      與intor不同float,分數(shù)不是 Python 中的內(nèi)置數(shù)據(jù)類型,這意味著您必須從標準庫中導(dǎo)入相應(yīng)的模塊才能使用它們。然而,一旦你通過了這額外的步驟,你會發(fā)現(xiàn)分數(shù)只是代表另一種數(shù)字類型,你可以在算術(shù)表達式中自由地與其他數(shù)字和數(shù)學(xué)運算符混合。

      注:分數(shù)是在純Python實現(xiàn)的并且是多比,可以直接在硬件上運行浮點數(shù)慢。在大多數(shù)需要大量計算的情況下,性能比精度更重要。另一方面,如果您同時需要性能和精度,那么可以考慮直接替換稱為quicktions 的分數(shù),這可能會在一定程度上提高您的執(zhí)行速度。

      在 Python 中有幾種創(chuàng)建分數(shù)的方法,它們都涉及使用Fraction類。這是您需要從fractions模塊導(dǎo)入的唯一內(nèi)容。類構(gòu)造函數(shù)接受零、一個或兩個不同類型的參數(shù):

      >>>

      >>> from fractions import Fraction >>> print(Fraction()) 0 >>> print(Fraction(0.75)) 3/4 >>> print(Fraction(3, 4)) 3/4

      當您調(diào)用不帶參數(shù)的類構(gòu)造函數(shù)時,它會創(chuàng)建一個表示數(shù)字零的新分數(shù)。單參數(shù)風格嘗試將另一種數(shù)據(jù)類型的值轉(zhuǎn)換為分數(shù)。傳入第二個參數(shù)使構(gòu)造函數(shù)需要一個分子和一個分母,它們必須是Rational類或其后代的實例。

      請注意,您必須print()用/分子和分母之間的斜杠字符 (?) 來表示分數(shù)以顯示其人性化的文本表示。如果不這樣做,它將退回到由一段 Python 代碼組成的稍微更明確的字符串表示形式。在本教程后面,您將學(xué)習(xí)如何將分數(shù)轉(zhuǎn)換為字符串。

      有理數(shù)

      當您Fraction()使用兩個參數(shù)調(diào)用構(gòu)造函數(shù)時,它們都必須是有理數(shù),例如整數(shù)或其他分數(shù)。如果分子或分母不是有理數(shù),則您將無法創(chuàng)建新分數(shù):

      >>>

      >>> Fraction(3, 4.0) Traceback (most recent call last): ... raise TypeError("both arguments should be " TypeError: both arguments should be Rational instances

      你得到一個TypeError。盡管4.0在數(shù)學(xué)中是有理數(shù),但 Python 并不認為它是有理數(shù)。這是因為該值存儲為浮點數(shù)據(jù)類型,該類型過于寬泛,可用于表示任何實數(shù)。

      注意:浮點數(shù)據(jù)類型不能精確地將無理數(shù)存儲在計算機的內(nèi)存中,因為它們是非終止和非重復(fù)的十進制擴展。但實際上,這沒什么大不了的,因為它們的近似值通常已經(jīng)足夠好了。唯一真正可靠的方法是需要對像 π 這樣的傳統(tǒng)符號使用符號計算。

      同樣,您不能創(chuàng)建分母為零的分數(shù),因為這會導(dǎo)致被零除,這是未定義的,在數(shù)學(xué)中沒有意義:

      >>>

      >>> Fraction(3, 0) Traceback (most recent call last): ... raise ZeroDivisionError('Fraction(%s, 0)' % numerator) ZeroDivisionError: Fraction(3, 0)

      Python 將ZeroDivisionError.?但是,當您指定有效的分子和有效的分母時,只要它們具有公約數(shù),它們就會自動為您標準化:

      >>>

      >>> Fraction(9, 12) # GCD(9, 12) = 3 Fraction(3, 4) >>> Fraction(0, 12) # GCD(0, 12) = 12 Fraction(0, 1)

      兩個量級都被它們的最大公約數(shù) (GCD)簡化,GCD恰好分別為 3 和 12。當您定義負分數(shù)時,歸一化也會考慮減號:

      >>>

      >>> -Fraction(9, 12) Fraction(-3, 4) >>> Fraction(-9, 12) Fraction(-3, 4) >>> Fraction(9, -12) Fraction(-3, 4)

      無論是在構(gòu)造函數(shù)之前還是在任何一個參數(shù)之前放置減號,為了保持一致性,Python 總是將分數(shù)的符號與其分子相關(guān)聯(lián)。目前有一種禁用此行為的方法,但它沒有記錄,將來可能會被刪除。

      您通常將分數(shù)定義為兩個整數(shù)的商。每當你只提供一個整數(shù),Python會變成這個數(shù)字為假分數(shù)假設(shè)分母為1:

      >>>

      >>> Fraction(3) Fraction(3, 1)

      相反,如果您跳過兩個參數(shù),則分子將為0:

      >>>

      >>> Fraction() Fraction(0, 1)

      不過,您不必總是為分子和分母提供整數(shù)。文檔說明它們可以是任何有理數(shù),包括其他分數(shù):

      >>>

      >>> one_third = Fraction(1, 3) >>> Fraction(one_third, 3) Fraction(1, 9) >>> Fraction(3, one_third) Fraction(9, 1) >>> Fraction(one_third, one_third) Fraction(1, 1)

      在每種情況下,您都會得到一個分數(shù)作為結(jié)果,即使它們有時表示像 9 和 1 這樣的整數(shù)值。稍后,您將看到如何將分數(shù)轉(zhuǎn)換為其他數(shù)據(jù)類型。

      如果你給Fraction構(gòu)造函數(shù)一個恰好也是一個分數(shù)的參數(shù)會發(fā)生什么?試試這個代碼找出:

      >>>

      >>> Fraction(one_third) == one_third True >>> Fraction(one_third) is one_third False

      您得到相同的值,但它是輸入分數(shù)的不同副本。那是因為調(diào)用構(gòu)造函數(shù)總是會產(chǎn)生一個新實例,這與分數(shù)是不可變的這一事實相吻合,就像 Python 中的其他數(shù)字類型一樣。

      浮點數(shù)和十進制數(shù)

      到目前為止,您只使用了有理數(shù)來創(chuàng)建分數(shù)。畢竟,F(xiàn)raction構(gòu)造函數(shù)的雙參數(shù)版本要求兩個數(shù)字都是Rational實例。但是,單參數(shù)構(gòu)造函數(shù)不是這種情況,它很樂意接受任何實數(shù),甚至是非數(shù)字值,例如字符串。

      Python 中實數(shù)數(shù)據(jù)類型的兩個主要示例是float和decimal.Decimal。雖然只有后者才能準確表示有理數(shù),但兩者都可以很好地近似無理數(shù)。與此相關(guān),如果您想知道,在這方面Fraction與Decimal此類似,因為它是Real.

      是Decimal實數(shù)嗎?顯示隱藏

      在 Python 3.2 之前,您只能使用 the.from_float()和.from_decimal()class 方法從實數(shù)創(chuàng)建分數(shù)。雖然沒有被棄用,但它們今天是多余的,因為Fraction構(gòu)造函數(shù)可以直接將兩種數(shù)據(jù)類型作為參數(shù):

      >>>

      >>> from decimal import Decimal >>> Fraction(0.75) == Fraction(Decimal("0.75")) True

      無論是Fraction從對象float還是Decimal對象創(chuàng)建對象,它們的值都是相同的。您之前已經(jīng)看到過從浮點值創(chuàng)建的分數(shù):

      >>>

      >>> print(Fraction(0.75)) 3/4

      結(jié)果是用小數(shù)表示法表示的相同數(shù)字。但是,此代碼僅出于巧合而按預(yù)期工作。在大多數(shù)情況下,由于影響float數(shù)字的表示錯誤,您將無法獲得預(yù)期值,無論它們是否有理:

      >>>

      >>> print(Fraction(0.1)) 3602879701896397/36028797018963968

      哇!這里發(fā)生了什么?

      讓我們用慢動作分解它。前面的數(shù)字可以表示為 0.75 或 3/4,也可以表示為 1/2 和 1/4 的和,它們是 2 的負冪,具有精確的二進制表示。另一方面,數(shù)字 ? 只能用二進制數(shù)字的非終止重復(fù)擴展來近似:

      由于有限的內(nèi)存,二進制字符串最終必須結(jié)束,所以它的尾部被四舍五入。默認情況下,Python 只顯示 中定義的最重要的數(shù)字sys.float_info.dig,但如果你想,你可以用任意數(shù)量的數(shù)字格式化一個浮點數(shù):

      >>>

      >>> str(0.1) '0.1' >>> format(0.1, ".17f") '0.10000000000000001' >>> format(0.1, ".18f") '0.100000000000000006' >>> format(0.1, ".19f") '0.1000000000000000056' >>> format(0.1, ".55f") '0.1000000000000000055511151231257827021181583404541015625'

      當您將 afloat或Decimal數(shù)字傳遞給Fraction構(gòu)造函數(shù)時,它會調(diào)用它們的.as_integer_ratio()方法來獲取兩個不可約整數(shù)的元組,其比率給出與輸入?yún)?shù)完全相同的十進制展開。然后將這兩個數(shù)字分配給新分數(shù)的分子和分母。

      注意:從 Python 3.8 開始,F(xiàn)raction還實現(xiàn)了.as_integer_ratio()對其他數(shù)字類型的補充。例如,它可以幫助您將分數(shù)轉(zhuǎn)換為元組。

      現(xiàn)在,您可以將這兩個大數(shù)字的來源拼湊起來:

      >>>

      >>> Fraction(0.1) Fraction(3602879701896397, 36028797018963968) >>> (0.1).as_integer_ratio() (3602879701896397, 36028797018963968)

      如果你拿出你的袖珍計算器并輸入這些數(shù)字,那么除法的結(jié)果就是 0.1。但是,如果您要手動分割它們或使用WolframAlpha 之類的工具,那么您最終會得到之前看到的小數(shù)點后 55 位。

      有一種方法可以找到具有更多實際值的分數(shù)的近似值。.limit_denominator()例如,您可以使用,您將在本教程后面了解更多信息:

      >>>

      >>> one_tenth = Fraction(0.1) >>> one_tenth Fraction(3602879701896397, 36028797018963968) >>> one_tenth.limit_denominator() Fraction(1, 10) >>> one_tenth.limit_denominator(max_denominator=int(1e16)) Fraction(1000000000000000, 9999999999999999)

      但是,這可能并不總是為您提供最佳近似值。最重要的是,您永遠不應(yīng)該嘗試直接從實數(shù)創(chuàng)建分數(shù),例如float如果您想避免可能出現(xiàn)的舍入錯誤。Decimal如果你不夠小心,即使是班級也可能會受到影響。

      不管怎樣,分數(shù)讓你用它們的構(gòu)造函數(shù)中的字符串最準確地傳達十進制符號。

      字符串

      Fraction構(gòu)造函數(shù)接受兩種字符串格式,分別對應(yīng)于十進制和小數(shù)表示法:

      >>>

      >>> Fraction("0.1") Fraction(1, 10) >>> Fraction("1/10") Fraction(1, 10)

      兩種記數(shù)法都可以選擇有加號 (?+) 或減號 (?-),而小數(shù)點還可以包括指數(shù),以防您想使用科學(xué)記數(shù)法:

      >>>

      >>> Fraction("-2e-3") Fraction(-1, 500) >>> Fraction("+2/1000") Fraction(1, 500)

      兩種結(jié)果的唯一區(qū)別是一種是負的,一種是正的。

      使用小數(shù)表示法時,不能在斜杠字符 (?/)周圍使用空格字符:

      >>>

      >>> Fraction("1 / 10") Traceback (most recent call last): ... raise ValueError('Invalid literal for Fraction: %r' % ValueError: Invalid literal for Fraction: '1 / 10'

      要找出到底哪些字符串是有效還是無效,你可以探索的正則表達式的模塊的源代碼。請記住從字符串或正確實例化的Decimal對象而不是float值創(chuàng)建分數(shù),以便您可以保持最大精度。

      既然您已經(jīng)創(chuàng)建了幾個分數(shù),您可能想知道除了將兩個數(shù)字組合在一起之外,它們還能為您做什么。這是一個很好的問題!

      檢查 Python 分數(shù)

      所述Rational抽象基類定義了兩個只讀屬性,用于訪問一個分數(shù)的分子和分母:

      >>>

      >>> from fractions import Fraction >>> half = Fraction(1, 2) >>> half.numerator 1 >>> half.denominator 2

      由于分數(shù)是不可變的,你不能改變它們的內(nèi)部狀態(tài):

      >>>

      >>> half.numerator = 2 Traceback (most recent call last): File "", line 1, in AttributeError: can't set attribute

      如果您嘗試為分數(shù)的一個屬性分配一個新值,則會出現(xiàn)錯誤。事實上,只要您想修改一個分數(shù),就必須創(chuàng)建一個新分數(shù)。例如,要反轉(zhuǎn)您的分數(shù),您可以調(diào)用.as_integer_ratio()獲取一個元組,然后使用切片語法反轉(zhuǎn)其元素:

      >>>

      >>> Fraction(*half.as_integer_ratio()[::-1]) Fraction(2, 1)

      一元星號運算符 (?*)解包您的反向元組并將其元素傳遞給Fraction構(gòu)造函數(shù)。

      每個分數(shù)附帶的另一種有用方法可讓您找到與十進制表示法中給出的數(shù)字最接近的有理數(shù)近似值。這就是.limit_denominator()您在本教程前面已經(jīng)提到的方法。您可以選擇為您的近似值請求最大分母:

      >>>

      >>> pi = Fraction("3.141592653589793") >>> pi Fraction(3141592653589793, 1000000000000000) >>> pi.limit_denominator(20_000) Fraction(62813, 19994) >>> pi.limit_denominator(100) Fraction(311, 99) >>> pi.limit_denominator(10) Fraction(22, 7)

      初始近似值可能不是最方便使用的,但它是最忠實的。此方法還可以幫助您恢復(fù)存儲為浮點數(shù)據(jù)類型的有理數(shù)。請記住float,即使它們具有終止的十進制擴展,也可能無法準確表示所有有理數(shù):

      >>>

      >>> pi = Fraction(3.141592653589793) >>> pi Fraction(884279719003555, 281474976710656) >>> pi.limit_denominator() Fraction(3126535, 995207) >>> pi.limit_denominator(10) Fraction(22, 7)

      與前面的代碼塊相比,您會注意到突出顯示的行上的結(jié)果不同,即使該float實例看起來與您之前傳遞給構(gòu)造函數(shù)的字符串文字相同!稍后,您將探索一個.limit_denominator()用于查找無理數(shù)近似值的示例。

      將 Python 分數(shù)轉(zhuǎn)換為其他數(shù)據(jù)類型

      您已經(jīng)學(xué)習(xí)了如何從以下數(shù)據(jù)類型創(chuàng)建分數(shù):

      str

      int

      float

      decimal.Decimal

      fractions.Fraction

      相反的呢?如何將Fraction實例轉(zhuǎn)換回這些類型?您將在本節(jié)中找到。

      浮點數(shù)和整數(shù)

      Python 中本機數(shù)據(jù)類型之間的轉(zhuǎn)換通常涉及調(diào)用內(nèi)置函數(shù)之一,例如int()或float()在對象上。只要對象實現(xiàn)了相應(yīng)的特殊方法,例如.__int__()或 ,這些轉(zhuǎn)換就會起作用.__float__()。分數(shù)恰好從Rational抽象基類繼承了后者:

      >>>

      >>> from fractions import Fraction >>> three_quarters = Fraction(3, 4) >>> float(three_quarters) 0.75 >>> three_quarters.__float__() # Don't call special methods directly 0.75 >>> three_quarters.__int__() Traceback (most recent call last): File "", line 1, in AttributeError: 'Fraction' object has no attribute '__int__'

      您不應(yīng)該直接在對象上調(diào)用特殊方法,但它有助于演示目的。在這里,您會注意到分數(shù)僅實現(xiàn).__float__()而不是.__int__().

      當您研究源代碼時,您會注意到該.__float__()方法方便地將分數(shù)的分子除以其分母得到一個浮點數(shù):

      >>>

      >>> three_quarters.numerator / three_quarters.denominator 0.75

      請記住,將Fraction實例轉(zhuǎn)換為float實例可能會導(dǎo)致有損轉(zhuǎn)換,這意味著您最終可能會得到一個稍微偏離的數(shù)字:

      >>>

      >>> float(Fraction(3, 4)) == Fraction(3, 4) True >>> float(Fraction(1, 3)) == Fraction(1, 3) False >>> float(Fraction(1, 10)) == Fraction(1, 10) False

      盡管分數(shù)不提供整數(shù)轉(zhuǎn)換的實現(xiàn),但所有實數(shù)都可以被截斷,這是int()函數(shù)的后備:

      >>>

      >>> fraction = Fraction(14, 5) >>> int(fraction) 2 >>> import math >>> math.trunc(fraction) 2 >>> fraction.__trunc__() # Don't call special methods directly 2

      稍后您將在有關(guān)舍入分數(shù)的部分中發(fā)現(xiàn)其他一些相關(guān)方法。

      十進制數(shù)

      如果您嘗試Decimal從Fraction實例創(chuàng)建數(shù)字,那么您很快就會發(fā)現(xiàn)這種直接轉(zhuǎn)換是不可能的:

      >>>

      >>> from decimal import Decimal >>> Decimal(Fraction(3, 4)) Traceback (most recent call last): File "", line 1, in TypeError: conversion from Fraction to Decimal is not supported

      當你嘗試時,你會得到一個TypeError.?但是,由于分數(shù)代表除法,您可以通過僅用其中一個數(shù)字包裹Decimal并手動除法來繞過該限制:

      >>>

      >>> fraction = Fraction(3, 4) >>> fraction.numerator / Decimal(fraction.denominator) Decimal('0.75')

      與 不同float但相似Fraction,Decimal數(shù)據(jù)類型沒有浮點表示錯誤。所以,當你轉(zhuǎn)換一個不能用二進制浮點精確表示的有理數(shù)時,你將保留數(shù)字的精度:

      >>>

      >>> fraction = Fraction(1, 10) >>> decimal = fraction.numerator / Decimal(fraction.denominator) >>> fraction == decimal True >>> fraction == 0.1 False >>> decimal == 0.1 False

      同時,不終止重復(fù)十進制擴展的有理數(shù)在從分數(shù)轉(zhuǎn)換為十進制表示法時會導(dǎo)致精度損失:

      >>>

      >>> fraction = Fraction(1, 3) >>> decimal = fraction.numerator / Decimal(fraction.denominator) >>> fraction == decimal False >>> decimal Decimal('0.3333333333333333333333333333')

      那是因為在三分之一或 的十進制展開中有無數(shù)個三Fraction(1, 3),而該Decimal類型具有固定精度。默認情況下,它只存儲二十八位小數(shù)。如果你愿意,你可以調(diào)整它,但它仍然是有限的。

      字符串

      分數(shù)的字符串表示使用熟悉的分數(shù)表示法顯示它們的值,而它們的規(guī)范表示輸出一段 Python 代碼,其中包含對Fraction構(gòu)造函數(shù)的調(diào)用:

      >>>

      >>> one_third = Fraction(1, 3) >>> str(one_third) '1/3' >>> repr(one_third) 'Fraction(1, 3)'

      無論使用str()或repr(),結(jié)果都是一個字符串,但它們的內(nèi)容不同。

      與其他數(shù)字類型不同,分數(shù)在 Python 中不支持字符串格式:

      >>>

      >>> from decimal import Decimal >>> format(Decimal("0.3333333333333333333333333333"), ".2f") '0.33' >>> format(Fraction(1, 3), ".2f") Traceback (most recent call last): File "", line 1, in TypeError: unsupported format string passed to Fraction.__format__

      如果你嘗試,那么你會得到一個TypeError.?Fraction例如,如果您想引用字符串模板中的實例來填充占位符,這可能是一個問題。另一方面,您可以通過將分數(shù)轉(zhuǎn)換為浮點數(shù)來快速解決這個問題,尤其是在這種情況下您不需要關(guān)心精度。

      如果您在Jupyter Notebook 中工作,那么您可能希望根據(jù)您的分數(shù)而不是它們的常規(guī)文本表示來呈現(xiàn)LaTeX公式。要做到這一點,必須猴補丁的Fraction加入了新的方法,數(shù)據(jù)類型._repr_pretty_(),其中Jupyter筆記本承認:

      from fractions import Fraction from IPython.display import display, Math Fraction._repr_pretty_ = lambda self, *args: \ display(Math(rf"$$\frac{{{self.numerator}}}{{{self.denominator}}}"))

      它將一段 LaTeX 標記包裝在一個Math對象中,并將其發(fā)送到筆記本的富顯示器,它可以使用MathJax庫呈現(xiàn)標記:

      下次評估包含F(xiàn)raction實例的筆記本單元格時,它將繪制漂亮的數(shù)學(xué)公式,而不是打印文本。

      對分數(shù)執(zhí)行有理數(shù)算術(shù)

      如前所述,您可以在由其他數(shù)字類型組成的算術(shù)表達式中使用分數(shù)。分數(shù)將與大多數(shù)數(shù)字類型互操作,除了decimal.Decimal,它有自己的一套規(guī)則。此外,另一個操作數(shù)的數(shù)據(jù)類型,無論它位于分數(shù)的左側(cè)還是右側(cè),都將決定算術(shù)運算結(jié)果的類型。

      添加

      您可以添加兩個或多個分數(shù),而無需考慮將它們減少到它們的公分母:

      >>>

      >>> from fractions import Fraction >>> Fraction(1, 2) + Fraction(2, 3) + Fraction(3, 4) Fraction(23, 12)

      結(jié)果是一個新的分數(shù),它是所有輸入分數(shù)的總和。當您將整數(shù)和分數(shù)相加時,也會發(fā)生同樣的情況:

      >>>

      >>> Fraction(1, 2) + 3 Fraction(7, 2)

      但是,一旦您開始將分數(shù)與非有理數(shù)(即不是其子類的數(shù)字)混合,那么numbers.Rational您的分數(shù)將在添加之前首先轉(zhuǎn)換為該類型:

      >>>

      >>> Fraction(3, 10) + 0.1 0.4 >>> float(Fraction(3, 10)) + 0.1 0.4

      無論您是否明確使用float().?該轉(zhuǎn)換可能會導(dǎo)致精度損失,因為您的分數(shù)以及結(jié)果現(xiàn)在都以浮點表示形式存儲。盡管數(shù)字 0.4 看起來是正確的,但它并不完全等于分數(shù) 4/10。

      減法

      減去分數(shù)與添加分數(shù)沒有什么不同。Python 會為你找到共同點:

      >>>

      >>> Fraction(3, 4) - Fraction(2, 3) - Fraction(1, 2) Fraction(-5, 12) >>> Fraction(4, 10) - 0.1 0.30000000000000004

      這一次,精度損失如此之大,一目了然。請注意在4十進制擴展末尾的一長串零后跟一個數(shù)字。這是四舍五入一個值的結(jié)果,否則將需要無限數(shù)量的二進制數(shù)字。

      乘法

      當您將兩個分數(shù)相乘時,它們的分子和分母會按元素相乘,如果需要,所得分數(shù)會自動減少:

      >>>

      >>> Fraction(1, 4) * Fraction(3, 2) Fraction(3, 8) >>> Fraction(1, 4) * Fraction(4, 5) # The result is 4/20 Fraction(1, 5) >>> Fraction(1, 4) * 3 Fraction(3, 4) >>> Fraction(1, 4) * 3.0 0.75

      同樣,根據(jù)另一個操作數(shù)的類型,您最終會在結(jié)果中得到不同的數(shù)據(jù)類型。

      分配

      Python 中有兩種除法運算符,分數(shù)支持這兩種運算符:

      真分裂:?/

      樓層劃分:?//

      真正的除法產(chǎn)生另一個分數(shù),而地板除法總是返回一個整數(shù),小數(shù)部分被截斷:

      >>>

      >>> Fraction(7, 2) / Fraction(2, 3) Fraction(21, 4) >>> Fraction(7, 2) // Fraction(2, 3) 5 >>> Fraction(7, 2) / 2 Fraction(7, 4) >>> Fraction(7, 2) // 2 1 >>> Fraction(7, 2) / 2.0 1.75 >>> Fraction(7, 2) // 2.0 1.0

      請注意,地板除法的結(jié)果并不總是整數(shù)!結(jié)果可能最終float取決于您與分數(shù)一起使用的數(shù)據(jù)類型。分數(shù)還支持模運算符 (?%)以及divmod()函數(shù),這可能有助于從不正確的分數(shù)創(chuàng)建混合分數(shù):

      >>>

      >>> def mixed(fraction): ... floor, rest = divmod(fraction.numerator, fraction.denominator) ... return f"{floor} and {Fraction(rest, fraction.denominator)}" ... >>> mixed(Fraction(22, 7)) '3 and 1/7'

      您可以更新函數(shù)以返回由整個部分和小數(shù)余數(shù)組成的元組,而不是像上面的輸出那樣生成字符串。繼續(xù)嘗試修改函數(shù)的返回值以查看差異。

      求冪

      您可以使用二元求冪運算符 (?**)或內(nèi)置pow()函數(shù)將分數(shù)求冪。您也可以使用分數(shù)本身作為指數(shù)。現(xiàn)在回到你的Python 解釋器,開始探索如何將分數(shù)提升為冪:

      >>>

      >>> Fraction(3, 4) ** 2 Fraction(9, 16) >>> Fraction(3, 4) ** (-2) Fraction(16, 9) >>> Fraction(3, 4) ** 2.0 0.5625

      您會注意到,您可以同時使用正指數(shù)值和負指數(shù)值。當指數(shù)不是Rational數(shù)字時,您的分數(shù)會float在繼續(xù)之前自動轉(zhuǎn)換為。

      當指數(shù)是一個Fraction實例時,事情會變得更加復(fù)雜。由于分數(shù)冪通常會產(chǎn)生無理數(shù),float除非底數(shù)和指數(shù)都是整數(shù),否則兩個操作數(shù)都將轉(zhuǎn)換為:

      >>>

      >>> 2 ** Fraction(2, 1) 4 >>> 2.0 ** Fraction(2, 1) 4.0 >>> Fraction(3, 4) ** Fraction(1, 2) 0.8660254037844386 >>> Fraction(3, 4) ** Fraction(2, 1) Fraction(9, 16)

      只有當指數(shù)的分母等于 1 并且您正在提高一個Fraction實例時,才會得到分數(shù)。

      舍入 Python 分數(shù)

      Python 中有許多四舍五入的策略,數(shù)學(xué)中還有更多。您可以對分數(shù)和十進制數(shù)使用相同的一組內(nèi)置全局和模塊級函數(shù)。他們會讓你為一個分數(shù)分配一個整數(shù),或者創(chuàng)建一個與更少小數(shù)位相對應(yīng)的新分數(shù)。

      當您將分數(shù)轉(zhuǎn)換為 a 時int,您已經(jīng)了解了一種粗略的舍入方法,它會截斷小數(shù)部分,只留下整個部分(如果有):

      >>>

      >>> from fractions import Fraction >>> int(Fraction(22, 7)) 3 >>> import math >>> math.trunc(Fraction(22, 7)) 3 >>> math.trunc(-Fraction(22, 7)) -3

      在這種情況下,調(diào)用int()等效于調(diào)用math.trunc(),它將正分數(shù)向下舍入,負分數(shù)向上舍入。這兩個操作分別稱為floor和天花板。如果你想,你可以直接使用兩者:

      >>>

      >>> math.floor(-Fraction(22, 7)) -4 >>> math.floor(Fraction(22, 7)) 3 >>> math.ceil(-Fraction(22, 7)) -3 >>> math.ceil(Fraction(22, 7)) 4

      比較的結(jié)果math.floor(),并math.ceil()與同期的來電math.trunc()。每個函數(shù)都有不同的舍入偏差,這可能會影響舍入數(shù)據(jù)集的統(tǒng)計屬性。幸運的是,有一種稱為四舍五入的策略,它比截斷、下限或上限更不偏向。

      本質(zhì)上,它會將您的分數(shù)四舍五入到最接近的整數(shù),同時為等距的一半選擇最接近的偶數(shù)。您可以致電round()以利用此策略:

      >>>

      >>> round(Fraction(3, 2)) # 1.5 2 >>> round(Fraction(5, 2)) # 2.5 2 >>> round(Fraction(7, 2)) # 3.5 4

      請注意這些分數(shù)是如何根據(jù)最接近的偶數(shù)的位置向上或向下舍入的?自然,這條規(guī)則僅適用于到左邊最近整數(shù)的距離與右邊相同的情況。否則,舍入方向基于到整數(shù)的最短距離,無論它是否為偶數(shù)。

      您可以選擇為該round()函數(shù)提供第二個參數(shù),該參數(shù)指示您要保留的小數(shù)位數(shù)。當你這樣做時,你總是會得到一個Fraction而不是一個整數(shù),即使你請求零位:

      >>>

      >>> fraction = Fraction(22, 7) # 3.142857142857143 >>> round(fraction, 0) Fraction(3, 1) >>> round(fraction, 1) # 3.1 Fraction(31, 10) >>> round(fraction, 2) # 3.14 Fraction(157, 50) >>> round(fraction, 3) # 3.143 Fraction(3143, 1000)

      但是,請注意調(diào)用round(fraction)和之間的區(qū)別round(fraction, 0),它產(chǎn)生相同的值但使用不同的數(shù)據(jù)類型:

      >>>

      >>> round(fraction) 3 >>> round(fraction, 0) Fraction(3, 1)

      當您省略第二個參數(shù)時,round()將返回最接近的整數(shù)。否則,您將得到一個減少的分數(shù),其分母最初是與您要求的十進制數(shù)相對應(yīng)的十的冪。

      在 Python 中比較分數(shù)

      在現(xiàn)實生活中,比較以分數(shù)表示的數(shù)字可能比比較以十進制表示的數(shù)字更困難,因為分數(shù)表示法由兩個值組成,而不僅僅是一個。為了理解這些數(shù)字,您通常將它們簡化為一個公分母并僅比較它們的分子。例如,嘗試根據(jù)它們的值按升序排列以下分數(shù):

      2/3

      5/8

      8/13

      它不如十進制表示法方便。混合符號會使情況變得更糟。然而,當你用一個公分母重寫這些分數(shù)時,對它們進行排序就變得很簡單了:

      208/312

      195/312

      192/312

      3、8 和 13 的最大公約數(shù)是 1。這意味著所有三個分數(shù)的最小公分母是它們的乘積 312。一旦您將所有分數(shù)轉(zhuǎn)換為使用它們的最小公分母,您就可以忽略分母并專注于比較分子。

      在 Python 中,當您比較和排序Fraction對象時,這在幕后工作:

      >>>

      >>> from fractions import Fraction >>> Fraction(8, 13) < Fraction(5, 8) True >>> sorted([Fraction(2, 3), Fraction(5, 8), Fraction(8, 13)]) [Fraction(8, 13), Fraction(5, 8), Fraction(2, 3)]

      Python 可以Fraction使用內(nèi)置sorted()函數(shù)快速對對象進行排序。有用的是,所有比較運算符都按預(yù)期工作。您甚至可以將它們用于其他數(shù)字類型,復(fù)數(shù)除外:

      >>>

      >>> Fraction(2, 3) < 0.625 False >>> from decimal import Decimal >>> Fraction(2, 3) < Decimal("0.625") False >>> Fraction(2, 3) < 3 + 2j Traceback (most recent call last): File "", line 1, in TypeError: '<' not supported between instances of 'Fraction' and 'complex'

      比較運算符處理浮點數(shù)和小數(shù),但是當您嘗試使用復(fù)數(shù)時會出錯3 + 2j。這與復(fù)數(shù)不定義自然排序關(guān)系的事實有關(guān),因此您無法將它們與任何東西進行比較——包括分數(shù)。

      選擇之間Fraction,Decimal和Float

      如果你需要選擇的只有一件事,從閱讀本教程記住,那么就應(yīng)該是當選擇Fraction了Decimal和float。所有這些數(shù)字類型都有其用例,因此最好了解它們的優(yōu)點和缺點。在本節(jié)中,您將簡要了解如何在這三種數(shù)據(jù)類型中的每一種中表示數(shù)字。

      二進制浮點:?float

      float在大多數(shù)情況下,數(shù)據(jù)類型應(yīng)該是表示實數(shù)的默認選擇。例如,它適用于執(zhí)行速度比精度更重要的科學(xué)、工程和計算機圖形學(xué)。幾乎沒有任何程序需要比浮點數(shù)更高的精度。

      注意:如果您只需要使用整數(shù),那么使用int速度和內(nèi)存效率更高的數(shù)據(jù)類型。

      浮點運算的無與倫比的速度源于它在硬件而不是軟件中的實現(xiàn)。幾乎所有數(shù)學(xué)協(xié)處理器都符合IEEE 754標準,該標準描述了如何以二進制浮點數(shù)表示數(shù)字。正如您所猜測的那樣,使用二進制系統(tǒng)的缺點是臭名昭著的表示錯誤。

      但是,除非您有特定的理由使用不同的數(shù)字類型,否則您應(yīng)該堅持使用float或int在可能的情況下使用。

      十進制浮點和定點:?Decimal

      有時使用二進制系統(tǒng)不能為實數(shù)提供足夠的精度。一個值得注意的例子是財務(wù)計算,它涉及同時處理非常大和非常小的數(shù)字。他們還傾向于一遍又一遍地重復(fù)相同的算術(shù)運算,這可能會累積顯著的舍入誤差。

      您可以使用十進制浮點算法存儲實數(shù)來緩解這些問題并消除二進制表示錯誤。它類似于float移動小數(shù)點以適應(yīng)更大或更小的幅度。但是,它以十進制而不是二進制運行。

      另一種提高數(shù)值精度的策略是定點算法,它為十進制擴展分配特定數(shù)量的數(shù)字。例如,最多四位小數(shù)的精度需要將分數(shù)存儲為按 10,000 倍放大的整數(shù)。為了恢復(fù)原始餾分,它們將相應(yīng)地按比例縮小。

      Python 的decimal.Decimal數(shù)據(jù)類型在底層是十進制浮點和定點表示的混合。它還遵循以下兩個標準:

      通用十進制算術(shù)規(guī)范 (?IBM?)

      基數(shù)無關(guān)的浮點運算 (?IEEE 854-1987?)

      它們是在軟件而不是硬件中模擬的,這使得這種數(shù)據(jù)類型在時間和空間方面的效率遠低于float.?另一方面,它可以表示具有任意但有限精度的數(shù)字,您可以自由調(diào)整。請注意,如果算術(shù)運算超過最大小數(shù)位數(shù),您仍可能面臨舍入錯誤。

      但是,今天固定精度提供的安全緩沖區(qū)明天可能會變得不足。考慮惡性通貨膨脹或處理匯率差異很大的多種貨幣,例如比特幣 (0.000029 BTC) 和伊朗里亞爾 (42,105.00 IRR)。如果您想要無限精度,請使用Fraction.

      無限精度有理數(shù):?Fraction

      無論是Fraction和Decimal類型共享一些相似之處。它們解決了二進制表示錯誤,它們是在軟件中實現(xiàn)的,您可以將它們用于貨幣應(yīng)用程序。盡管如此,分數(shù)的主要用途是表示有理數(shù),因此與小數(shù)相比,它們在存儲貨幣方面可能不太方便。

      注意:雖然Fraction數(shù)據(jù)類型是在純 Python 中實現(xiàn)的,但大多數(shù) Python 發(fā)行版都為該Decimal類型提供了一個編譯的動態(tài)鏈接庫。如果它不適用于您的平臺,那么 Python 也會回退到純 Python 實現(xiàn)。但是,即使是編譯版本也不會像以前那樣充分利用硬件float。

      使用Fractionover有兩個優(yōu)點Decimal。第一個是無限精度,僅受可用內(nèi)存的限制。這使您可以使用非終止和重復(fù)的十進制擴展來表示有理數(shù),而不會丟失任何信息:

      >>>

      >>> from fractions import Fraction >>> one_third = Fraction(1, 3) >>> print(3 * one_third) 1 >>> from decimal import Decimal >>> one_third = 1 / Decimal(3) >>> print(3 * one_third) 0.9999999999999999999999999999

      將 1/3 乘以 3 可以得到小數(shù)形式中的 1,但結(jié)果以十進制形式四舍五入。它有二十八位小數(shù),這是該Decimal類型的默認精度。

      再看看分數(shù)的另一個好處,你之前已經(jīng)開始學(xué)習(xí)了。與 不同Decimal,分數(shù)可以與二進制浮點數(shù)互操作:

      >>>

      >>> Fraction("0.75") - 0.25 0.5 >>> Decimal("0.75") - 0.25 Traceback (most recent call last): File "", line 1, in TypeError: unsupported operand type(s) for -: 'decimal.Decimal' and 'float'

      當您將分數(shù)與浮點數(shù)混合時,您會得到一個浮點數(shù)。另一方面,如果您嘗試將分數(shù)與Decimal數(shù)據(jù)類型混合使用,則會遇到TypeError.

      在行動中研究 Python 分數(shù)

      在本節(jié)中,您將通過一些有趣且實用的示例Fraction在 Python中使用數(shù)據(jù)類型。您可能會驚訝于分數(shù)有多么方便,同時它們又被低估了多少。準備好潛水吧!

      逼近無理數(shù)

      無理數(shù)在數(shù)學(xué)中扮演著重要的角色,這就是它們與許多子領(lǐng)域(如算術(shù)、微積分和幾何)相切的原因。您之前可能聽說過的一些最著名的有:

      二的平方根 (√2)

      阿基米德常數(shù) (π)

      黃金比例(φ)

      歐拉數(shù) (?e?)

      在數(shù)學(xué)史上,pi (π) 一直特別有趣,這導(dǎo)致了許多嘗試為其找到準確的近似值。

      雖然古代哲學(xué)家必須竭盡全力,但今天您可以使用 Python 使用蒙特卡羅方法(例如布馮針法或類似方法)找到非常好的 pi 估計值。然而,在大多數(shù)日常問題中,只有一個方便分數(shù)形式的粗略近似就足夠了。以下是如何確定兩個整數(shù)的商,從而逐漸更好地逼近無理數(shù):

      from fractions import Fraction from itertools import count def approximate(number): history = set() for max_denominator in count(1): fraction = Fraction(number).limit_denominator(max_denominator) if fraction not in history: history.add(fraction) yield fraction

      該函數(shù)接受一個無理數(shù),將其轉(zhuǎn)換為分數(shù),然后找到一個小數(shù)位數(shù)較少的不同分數(shù)。在Python的設(shè)置防止通過保持歷史數(shù)據(jù)生成重復(fù)值,以及itertools模塊的count()迭代器計數(shù)到無窮遠。

      您現(xiàn)在可以使用此函數(shù)來查找 pi 的前十個分數(shù)近似值:

      >>>

      >>> from itertools import islice >>> import math >>> for fraction in islice(approximate(math.pi), 10): ... print(f"{str(fraction):>7}", "→", float(fraction)) ... 3 → 3.0 13/4 → 3.25 16/5 → 3.2 19/6 → 3.1666666666666665 22/7 → 3.142857142857143 179/57 → 3.1403508771929824 201/64 → 3.140625 223/71 → 3.140845070422535 245/78 → 3.141025641025641 267/85 → 3.1411764705882352

      好的!有理數(shù) 22/7 已經(jīng)很接近了,這表明 pi 可以在早期近似,畢竟不是特別無理。該islice()迭代接收請求的10個值之前停止無限迭代。繼續(xù)并通過增加結(jié)果數(shù)量或?qū)ふ移渌麩o理數(shù)的近似值來玩這個例子。

      獲取顯示器的縱橫比

      圖像或顯示器的縱橫比是其寬度與高度的商,可以方便地表示比例。它通常用于電影和數(shù)字媒體,而電影導(dǎo)演喜歡利用縱橫比作為一種藝術(shù)衡量標準。如果您一直在尋找新的智能手機,那么規(guī)格可能會提到屏幕比例,例如 16:9。

      您可以通過使用官方 Python 發(fā)行版附帶的Tkinter測量其寬度和高度來找出計算機顯示器的縱橫比:

      >>>

      >>> import tkinter as tk >>> window = tk.Tk() >>> window.winfo_screenwidth() 2560 >>> window.winfo_screenheight() 1440

      請注意,如果您連接了多個顯示器,則此代碼可能無法按預(yù)期工作。

      計算縱橫比是創(chuàng)建一個會減少自身的分數(shù)的問題:

      >>>

      >>> from fractions import Fraction >>> Fraction(2560, 1440) Fraction(16, 9)

      你去吧。顯示器的分辨率為 16:9。但是,如果您使用的是屏幕尺寸較小的筆記本電腦,那么您的分數(shù)一開始可能無法計算,您需要相應(yīng)地限制其分母:

      >>>

      >>> Fraction(1360, 768) Fraction(85, 48) >>> Fraction(1360, 768).limit_denominator(10) Fraction(16, 9)

      請記住,如果您正在處理移動設(shè)備的垂直屏幕,則應(yīng)交換尺寸,使第一個尺寸大于下一個尺寸。您可以將此邏輯封裝在一個可重用的函數(shù)中:

      from fractions import Fraction def aspect_ratio(width, height, max_denominator=10): if height > width: width, height = height, width ratio = Fraction(width, height).limit_denominator(max_denominator) return f"{ratio.numerator}:{ratio.denominator}"

      無論參數(shù)順序如何,這都將確保一致的縱橫比:

      >>>

      >>> aspect_ratio(1080, 2400) '20:9' >>> aspect_ratio(2400, 1080) '20:9'

      無論您是查看水平屏幕還是垂直屏幕的測量值,縱橫比都是相同的。

      到目前為止,寬度和高度都是整數(shù),但是小數(shù)值呢?例如,某些佳能相機具有 APS-C 裁切傳感器,其尺寸為 22.8 毫米 x 14.8 毫米。分數(shù)會阻塞浮點數(shù)和十進制數(shù),但您可以將它們轉(zhuǎn)換為有理數(shù)近似值:

      >>>

      >>> aspect_ratio(22.2, 14.8) Traceback (most recent call last): ... raise TypeError("both arguments should be " TypeError: both arguments should be Rational instances >>> aspect_ratio(Fraction("22.2"), Fraction("14.8")) '3:2'

      在這種情況下,縱橫比正好是 1.5 或 3:2,但許多相機使用稍長的傳感器寬度,其比例為 1.555…或 14:9。當您進行數(shù)學(xué)運算時,您會發(fā)現(xiàn)它是寬幅 (16:9)和四分之三制 (4:3)的算術(shù)平均值,這是一種折衷,可以讓您以可接受的方式顯示圖片這兩種流行的格式。

      計算照片的曝光值

      在數(shù)字圖像中嵌入元數(shù)據(jù)的標準格式Exif(可交換圖像文件格式)使用比率來存儲多個值。一些最重要的比率描述了照片的曝光度:

      光圈值

      接觸時間

      曝光偏差

      焦距

      光圈

      快門速度

      快門速度通俗地與曝光時間同義,但它使用基于對數(shù)刻度的APEX 系統(tǒng)作為元數(shù)據(jù)中的一小部分存儲。這意味著相機會取曝光時間的倒數(shù),然后計算它的對數(shù)以 2 為底。因此,例如,第二個曝光時間的 1/200 將作為 7643856/1000000 寫入文件。計算方法如下:

      >>>

      >>> from fractions import Fraction >>> exposure_time = Fraction(1, 200) >>> from math import log2, trunc >>> precision = 1_000_000 >>> trunc(log2(Fraction(1, exposure_time)) * precision) 7643856

      如果您在沒有任何外部庫幫助的情況下手動讀取此元數(shù)據(jù),您可以使用 Python 分數(shù)來恢復(fù)原始曝光時間:

      >>>

      >>> shutter_speed = Fraction(7643856, 1_000_000) >>> Fraction(1, round(2 ** shutter_speed)) Fraction(1, 200)

      當您將拼圖的各個部分(即光圈、快門速度和 ISO 速度)組合在一起時,您將能夠計算出單個曝光值 (EV),它描述了捕獲光的平均量。然后,您可以使用它來推導(dǎo)出拍攝場景中亮度的對數(shù)平均值,這在后期處理和應(yīng)用特殊效果方面非常寶貴。

      曝光值的計算公式如下:

      from math import log2 def exposure_value(f_stop, exposure_time, iso_speed): return log2(f_stop ** 2 / exposure_time) - log2(iso_speed / 100)

      請記住,它沒有考慮其他因素,例如您的相機可能適用的曝光偏差或閃光燈。無論如何,嘗試一些示例值:

      >>>

      >>> exposure_value( ... f_stop=Fraction(28, 5), ... exposure_time=Fraction(1, 750), ... iso_speed=400 ... ) 12.521600439723727 >>> exposure_value(f_stop=5.6, exposure_time=1/750, iso_speed=400) 12.521600439723727

      您可以將分數(shù)或其他數(shù)字類型用于輸入值。這種情況下,曝光值在+13左右,比較亮。這張照片是在陽光明媚的日子在外面拍攝的,盡管是在陰涼處。

      解決變革問題

      您可以使用分數(shù)來解決計算機科學(xué)的經(jīng)典變革問題,您可能會在求職面試中遇到這些問題。它要求最少數(shù)量的硬幣來獲得一定數(shù)量的錢。例如,如果您考慮最流行的美元硬幣,那么您可以將 2.67美元表示為十個四分之一 (10 × $0.25)、一角硬幣 (1 × $0.10)、一鎳 (1 × $0.05) 和兩便士 (2 × 0.01 美元)。

      分數(shù)可以是一種方便的工具來表示錢包或收銀機中的硬幣。您可以通過以下方式定義美元的硬幣:

      from fractions import Fraction penny = Fraction(1, 100) nickel = Fraction(5, 100) dime = Fraction(10, 100) quarter = Fraction(25, 100)

      其中一些會自動減少自己,但這沒關(guān)系,因為您將使用十進制表示法對其進行格式化。您可以使用這些硬幣來計算您錢包的總價值:

      >>>

      >>> wallet = [8 * quarter, 5 * dime, 3 * nickel, 2 * penny] >>> print(f"${float(sum(wallet)):.2f}") $2.67

      您的錢包價值 2.67 美元,但里面有多達 18 個硬幣。可以使用更少的硬幣獲得相同的數(shù)量。解決變革問題的一種方法是使用貪婪算法,例如:

      def change(amount, coins): while amount > 0: for coin in sorted(coins, reverse=True): if coin <= amount: amount -= coin yield coin break else: raise Exception("There's no solution")

      該算法嘗試找到面額最高且不大于剩余金額的硬幣。雖然實施起來相對簡單,但它可能無法在所有硬幣系統(tǒng)中提供最佳解決方案。以下是美元硬幣的示例:

      >>>

      >>> from collections import Counter >>> amount = Fraction("2.67") >>> usd = [penny, nickel, dime, quarter] >>> for coin, count in Counter(change(amount, usd)).items(): ... print(f"{count:>2} × ${float(coin):.2f}") ... 10 × $0.25 1 × $0.10 1 × $0.05 2 × $0.01

      必須使用有理數(shù)才能找到解決方案,因為浮點值不會削減它。由于change()是生成可能重復(fù)的硬幣的生成器函數(shù),因此您可以Counter將它們分組。

      您可以通過提出一個稍微不同的問題來修改這個問題。例如,考慮到總價、顧客的硬幣和收銀機中可用的賣方硬幣,最佳的硬幣集是什么?

      生產(chǎn)和擴大連續(xù)餾分

      在本教程的開頭,您了解到無理數(shù)可以表示為無限連分數(shù)。此類分數(shù)需要無限量的內(nèi)存才能存在,但您可以選擇何時停止生成它們的系數(shù)以獲得合理的近似值。

      以下生成器函數(shù)將以惰性求值的方式無休止地產(chǎn)生給定數(shù)字的系數(shù):

      1def continued_fraction(number): 2 while True: 3 yield (whole_part := int(number)) 4 fractional_part = number - whole_part 5 try: 6 number = 1 / fractional_part 7 except ZeroDivisionError: 8 break

      該函數(shù)截斷數(shù)字并繼續(xù)將剩余分數(shù)表示為作為輸入反饋的倒數(shù)。為了消除代碼重復(fù),它在第 3 行使用賦值表達式,通常稱為Python 3.8 中引入的walrus 運算符。

      有趣的是,您也可以為有理數(shù)創(chuàng)建連分數(shù):

      >>>

      >>> list(continued_fraction(42)) [42] >>> from fractions import Fraction >>> list(continued_fraction(Fraction(3, 4))) [0, 1, 3]

      數(shù)字 42 只有一個系數(shù),沒有小數(shù)部分。相反,3/4 沒有整數(shù)部分,而是由 1 超過 1 + 1/3 組成的連分數(shù):

      像往常一樣,您應(yīng)該注意在切換到 時可能會出現(xiàn)的浮點表示錯誤float:

      >>>

      >>> list(continued_fraction(0.75)) [0, 1, 3, 1125899906842624]

      雖然您可以忠實地用二進制表示 0.75,但它的倒數(shù)盡管是有理數(shù),但具有非終止的十進制擴展。當您檢查其余的系數(shù)時,您最終會在分母中達到這個巨大的幅度,代表一個可以忽略不計的小值。那是你的近似誤差。

      您可以通過用 Python 分數(shù)替換實數(shù)來消除此錯誤:

      from fractions import Fraction def continued_fraction(number): while True: yield (whole_part := int(number)) fractional_part = Fraction(number) - whole_part try: number = Fraction(1, fractional_part) except ZeroDivisionError: break

      這個小改動可以讓您可靠地生成對應(yīng)于十進制數(shù)的連分數(shù)的系數(shù)。否則,即使終止十進制擴展,您也可能陷入無限循環(huán)。

      好的,讓我們做一些更有趣的事情,生成無理數(shù)的系數(shù),其中的小數(shù)展開式在小數(shù)點后第 50 位被砍掉。為了精確起見,將它們定義為Decimal實例:

      >>>

      >>> from decimal import Decimal >>> pi = Decimal("3.14159265358979323846264338327950288419716939937510") >>> sqrt2 = Decimal("1.41421356237309504880168872420969807856967187537694") >>> phi = Decimal("1.61803398874989484820458683436563811772030917980576")

      現(xiàn)在,您可以使用熟悉的islice()迭代器檢查其連分數(shù)的前幾個系數(shù):

      >>>

      >>> from itertools import islice >>> numbers = { ... " π": pi, ... "√2": sqrt2, ... " φ": phi ... } >>> for label, number in numbers.items(): ... print(label, list(islice(continued_fraction(number), 20))) ... π [3, 7, 15, 1, 292, 1, 1, 1, 2, 1, 3, 1, 14, 2, 1, 1, 2, 2, 2, 2] √2 [1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2] φ [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

      pi 的前四個系數(shù)給出了驚人的好近似值,后面是微不足道的余數(shù)。然而,另外兩個常數(shù)的連分數(shù)看起來很奇特。他們一遍又一遍地重復(fù)相同的數(shù)字,直到無窮大。知道了這一點,您可以通過將這些系數(shù)擴展回十進制形式來近似它們:

      def expand(coefficients): if len(coefficients) > 1: return coefficients[0] + Fraction(1, expand(coefficients[1:])) else: return Fraction(coefficients[0])

      遞歸地定義這個函數(shù)很方便,這樣它就可以在連續(xù)較小的系數(shù)列表上調(diào)用自己。在基本情況下,只有一個整數(shù),這是可能的最粗略的近似值。如果有兩個或更多,則結(jié)果是第一個系數(shù)的總和,然后是其余系數(shù)的倒數(shù)展開。

      您可以通過在相反的返回值上調(diào)用它們來驗證這兩個函數(shù)是否按預(yù)期工作:

      >>>

      >>> list(continued_fraction(3.14159)) [3, 7, 15, 1, 25, 1, 7, 4, 851921, 1, 1, 2, 880, 1, 2] >>> float(expand([3, 7, 15, 1, 25, 1, 7, 4, 851921, 1, 1, 2, 880, 1, 2])) 3.14159

      完美的!如果你輸入continued_fraction()to的結(jié)果expand(),那么你會得到你在開始時的初始值。但是,在某些情況下,您可能需要將擴展 分數(shù)轉(zhuǎn)換為Decimal類型而不是float更高的精度。

      結(jié)論

      在閱讀本教程之前,您可能從未想過計算機如何存儲小數(shù)。畢竟,也許你的好老朋友似乎可以很好float地處理它們。然而,歷史表明,這種誤解最終可能導(dǎo)致災(zāi)難性的失敗,而這可能會花費大量資金。

      使用 PythonFraction是避免此類災(zāi)難的一種方法。您已經(jīng)了解了分數(shù)表示法的優(yōu)缺點、它的實際應(yīng)用以及在 Python 中使用它的方法。現(xiàn)在,您可以就哪種數(shù)字類型最適合您的用例做出明智的選擇。

      在本教程中,您學(xué)習(xí)了如何:

      在十進制和小數(shù)表示法之間轉(zhuǎn)換

      執(zhí)行有理數(shù)算術(shù)

      近似無理數(shù)

      以無限精度精確表示分數(shù)

      知道什么時候選擇?Fraction了Decimal或float

      Python 數(shù)據(jù)結(jié)構(gòu)

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔相應(yīng)法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:第十一屆藍橋杯決賽JavaC組真題——詳細答案對照(完整版)
      下一篇:excel取消表格分頁符的方法教程(Excel表格取消分頁符)
      相關(guān)文章
      永久亚洲成a人片777777 | 亚洲中文字幕无码一久久区| 亚洲精品天堂成人片AV在线播放| 亚洲视频免费在线看| 亚洲最大的成网4438| 亚洲天堂中文字幕| 久久亚洲中文字幕精品有坂深雪 | 亚洲熟妇久久精品| 久久综合久久综合亚洲| 亚洲日日做天天做日日谢| 亚洲人成在线精品| 亚洲欧洲日本在线观看| 亚洲日本久久久午夜精品| 亚洲熟妇成人精品一区| 亚洲中文无码亚洲人成影院| 伊人久久五月丁香综合中文亚洲| 亚洲欧美国产日韩av野草社区| 亚洲欧洲无码AV不卡在线| 亚洲JIZZJIZZ妇女| 国产精品亚洲专区无码唯爱网 | 午夜亚洲AV日韩AV无码大全| 亚洲午夜在线电影| 亚洲福利视频网址| 国产午夜亚洲精品| 国产精品无码亚洲一区二区三区| 偷自拍亚洲视频在线观看| 亚洲色图综合在线| 国产亚洲精品自在久久| 亚洲国产女人aaa毛片在线| 亚洲美女视频网站| 色婷五月综激情亚洲综合| 亚洲码欧美码一区二区三区| 国产亚洲综合视频| 亚洲欧洲日产国码无码久久99| 亚洲国产精品一区| 亚洲a级在线观看| 亚洲av永久中文无码精品| 亚洲精品国产成人影院| 亚洲精品tv久久久久久久久 | 国产精品亚洲综合| 亚洲性猛交XXXX|