การ จำกัด การลอยให้เป็นทศนิยมสองตำแหน่ง


1691

ฉันต้องการaที่จะปัดเศษ13.95

>>> a
13.949999999999999
>>> round(a, 2)
13.949999999999999

roundฟังก์ชั่นไม่ได้ทำงานในแบบที่ผมคาดไว้



6
อืม ... คุณพยายามเป็นตัวแทนของสกุลเงินเหรอ? ถ้าเป็นเช่นนั้นคุณไม่ควรใช้เงินเป็นดอลลาร์ คุณอาจใช้ลอยสำหรับเพนนีหรือหน่วยสกุลเงินที่เล็กที่สุดที่คุณพยายามสร้างแบบจำลอง แต่วิธีปฏิบัติที่ดีที่สุดคือการใช้การแทนทศนิยมตามที่ HUAGHAGUAH แนะนำในคำตอบของเขา
SingleNegationElimination

63
มันเป็นสิ่งสำคัญที่จะไม่แสดงสกุลเงินในการลอย ลอยไม่แม่นยำ แต่จำนวนเงินเพนนีหรือเซ็นต์เป็นจำนวนเต็ม ดังนั้นจำนวนเต็มเป็นวิธีที่ถูกต้องในการเป็นตัวแทนของสกุลเงิน
Davoud Taghawi-Nejad

2
@ DavoudTaghawi-Nejad หรือมากกว่านั้น ... The Decimal Type
พื้นฐาน

17
ฉันอาจมาสายเกินไปที่นี่ แต่ฉันอยากถามนักพัฒนาของ Python แก้ปัญหานี้ไหม เพราะเมื่อฉันปัดเศษ (13.949999999999999, 2) ฉันก็จะได้ 13.95 ฉันได้ลองใน Python 2.7.6 และ 3.4 แล้ว มันได้ผล. ไม่แน่ใจว่า 2.7 เคยมีในปี 2009 บางทีมันเป็น Python 2.5 หรือเปล่า
bad_keypoints

คำตอบ:


1690

คุณกำลังประสบปัญหาเก่ากับตัวเลขจุดลอยตัวที่ไม่สามารถแสดงตัวเลขทั้งหมดได้อย่างแม่นยำ บรรทัดคำสั่งกำลังแสดงรูปแบบจุดลอยตัวเต็มรูปแบบจากหน่วยความจำ

ด้วยการเป็นตัวแทนจุดลอยรุ่นกลมของคุณเป็นจำนวนเดียวกัน เนื่องจากคอมพิวเตอร์เป็นเลขฐานสองพวกเขาจะเก็บตัวเลขทศนิยมเป็นจำนวนเต็มแล้วหารด้วยกำลังสองดังนั้น 13.95 จะแสดงในรูปแบบที่คล้ายกันกับ 125650429603636838 / (2 ** 53)

ตัวเลขความแม่นยำสองเท่ามีความแม่นยำ 53 บิต (16 หลัก) และลอยปกติมีความแม่นยำ 24 บิต (8 หลัก) ชนิดลอยจุดในหลามใช้ความแม่นยำสองในการจัดเก็บค่า

ตัวอย่างเช่น,

>>> 125650429603636838/(2**53)
13.949999999999999

>>> 234042163/(2**24)
13.949999988079071

>>> a = 13.946
>>> print(a)
13.946
>>> print("%.2f" % a)
13.95
>>> round(a,2)
13.949999999999999
>>> print("%.2f" % round(a, 2))
13.95
>>> print("{:.2f}".format(a))
13.95
>>> print("{:.2f}".format(round(a, 2)))
13.95
>>> print("{:.15f}".format(round(a, 2)))
13.949999999999999

หากคุณอยู่หลังตำแหน่งทศนิยมเพียงสองตำแหน่งเท่านั้น (เพื่อแสดงค่าสกุลเงิน) คุณจะมีตัวเลือกที่ดีกว่า:

  1. ใช้จำนวนเต็มและเก็บค่าเป็นเซ็นต์ไม่ใช่ดอลลาร์แล้วหารด้วย 100 เพื่อแปลงเป็นดอลลาร์
  2. หรือใช้เป็นจำนวนจุดคงเหมือนทศนิยม

27
@Christian มีความแตกต่างพื้นฐานระหว่างค่าที่จัดเก็บและวิธีที่คุณแสดงค่านั้น การจัดรูปแบบผลลัพธ์ควรอนุญาตให้คุณเพิ่มการเติมได้ตามต้องการรวมถึงการเพิ่มตัวคั่นด้วยเครื่องหมายจุลภาค ฯลฯ
พื้นฐาน

22
มูลค่าการกล่าวขวัญว่า"%.2f" % round(a,2)คุณสามารถใส่ไม่เพียง แต่ใน printf แต่ยังในสิ่งต่าง ๆ เช่นstr()
andilabs

20
ทำไมผู้คนมักคิดว่าสกุลเงินในการปัดเศษทศนิยม บางครั้งคุณแค่ต้องการทำงานด้วยความแม่นยำน้อยลง
worc

9
@radtek: คุณต้องเข้าใจว่าค่าไบนารี (ชนิดfloat) เป็นเพียงการประมาณที่ใกล้ที่สุดของเลขทศนิยม (ที่คุณคุ้นเคยกับมนุษย์) ไม่มีค่าเลขฐานสองดังกล่าว (สามารถแสดงได้อย่างละเอียด) เป็น 0.245 มันก็ไม่อยู่และคณิตศาสตร์ไม่สามารถมีชีวิตอยู่ ค่าไบนารีที่ใกล้เคียงกับ 0.245 น้อยกว่า 0.245 เล็กน้อยดังนั้นโดยทั่วไปแล้วจะปัดเศษลง ในทำนองเดียวกันไม่มีสิ่งเช่น 0.225 ในไบนารี แต่ค่าไบนารีซึ่งใกล้เคียงกับ 0.225 มากกว่า 0.225 เล็กน้อยดังนั้นโดยธรรมชาติมันจะปัดขึ้น
John Y

12
@radtek: คุณขอคำอธิบายอย่างแท้จริง ทางออกที่ตรงไปตรงมาที่สุดคือการใช้Decimalและนั่นก็เป็นหนึ่งในโซลูชั่นที่นำเสนอในคำตอบนี้ อีกอันคือการแปลงปริมาณของคุณเป็นจำนวนเต็มและใช้เลขคณิตเลขจำนวนเต็ม ทั้งสองวิธีนี้ยังปรากฏในคำตอบและความคิดเห็นอื่น ๆ
John Y

586

มีข้อกำหนดรูปแบบใหม่ข้อกำหนดรูปแบบสตริงเป็นภาษามินิ :

คุณสามารถทำเช่นเดียวกับ:

"{:.2f}".format(13.949999999999999)

หมายเหตุ 1:ข้างต้นส่งกลับสตริง หากต้องการลอยตัวให้ห่อด้วยfloat(...):

float("{:.2f}".format(13.949999999999999))

หมายเหตุ 2: การหุ้มด้วยfloat()จะไม่เปลี่ยนแปลงอะไรเลย:

>>> x = 13.949999999999999999
>>> x
13.95
>>> g = float("{:.2f}".format(x))
>>> g
13.95
>>> x == g
True
>>> h = round(x, 2)
>>> h
13.95
>>> x == h
True

17
เพื่อเพิ่มเครื่องหมายจุลภาคเช่นกันคุณสามารถ'{0:,.2f}'.format(1333.949999999)พิมพ์'1,333.95'ได้
สตีเฟ่นบลัม

@ OnurYıldırım: ใช่ แต่คุณสามารถห่อมันด้วยfloat(); float("{0:.2f}".format(13.9499999))
Jossef Harush

5
@JossefHarush คุณสามารถห่อด้วย float () แต่คุณยังไม่ได้รับอะไรเลย ตอนนี้คุณมีการลอยอีกครั้งโดยมีความไม่แน่นอนเหมือนกันทั้งหมด 13.9499999999999 และ 13.95 เป็นทศนิยมเดียวกัน
Ned Batchelder

4
@NedBatchelder: ผมเห็นว่าพวกเขามีค่าเท่ากัน แต่ข้อ จำกัด นี้ลอยไปสองจุดทศนิยม :)
Jossef Harush

8
อย่างไรก็ตามตั้งแต่ Python 3.6 เราสามารถใช้ f-strings:f"Result is {result:.2f}"
Andrey Semakin

289

built-in ใช้round()งานได้ดีใน Python 2.7 หรือใหม่กว่า

ตัวอย่าง:

>>> round(14.22222223, 2)
14.22

ตรวจสอบเอกสาร


1
ดังนั้นฉันจะเข้าใจว่านี่เป็น Python 2.7 ล้มเหลวหรือไม่ เหตุใดฟังก์ชันพื้นฐานจึงให้ผลลัพธ์ที่แตกต่างจาก v 2.7 ถึง v 3
MikeM

แต่round(2.16, 1)ให้2.2เหตุผลว่าทำไมไพ ธ อนจึงเสนอtruncatefunc
jiamo

ตัวอย่างเช่นหากคุณพยายามปัดค่า 2.675 ไปเป็นทศนิยมสองตำแหน่งคุณจะได้รับ>>> round(2.675, 2) 2.67 docs.python.org/2/tutorial/floatingpoint.html
danger89

4
จากหน้าเอกสาร Python 3:Note The behavior of round() for floats can be surprising: for example, round(2.675, 2) gives 2.67 instead of the expected 2.68. This is not a bug: it’s a result of the fact that most decimal fractions can’t be represented exactly as a float.
Richard Dally

โปรดทราบว่าหากคุณพยายามใช้วิธีการนี้เพื่อพิมพ์ตัวเลขเช่น 1.00000 มันจะพิมพ์ออกมาเพียง 1.0 ไม่ว่าคุณจะระบุจำนวนทศนิยมเท่าใด
Josh Correia

142

ฉันรู้สึกว่าวิธีที่ง่ายที่สุดคือการใช้format()ฟังก์ชั่น

ตัวอย่างเช่น:

a = 13.949999999999999
format(a, '.2f')

13.95

สิ่งนี้สร้างจำนวนลอยเป็นสตริงที่ปัดเศษเป็นทศนิยมสองตำแหน่ง


97

ใช้

print"{:.2f}".format(a)

แทน

print"{0:.2f}".format(a)

เพราะหลังอาจนำไปสู่ข้อผิดพลาดการส่งออกเมื่อพยายามที่จะส่งออกหลายตัวแปร (ดูความคิดเห็น)


2
นี่เป็นเรื่องไร้สาระ ประโยคทั้งสองที่กำหนดให้ทำงานเหมือนกันบน Python 2.7 และมีเพียงประโยคที่สองเท่านั้นที่ใช้ได้กับ Python 2.6 (ทั้งคำสั่งไม่ถูกต้องใน Python 3 หรือ Python <2.6.) รูปแบบแรกไม่มีประโยชน์นอกเหนือจากความกะทัดรัด
Mark Dickinson

1
ฉันหมายถึงพิมพ์ "{0: .2f} {0: .2f}" รูปแบบ (a, b) จะนำไปสู่ข้อผิดพลาดในการส่งออก - มันจะส่งออก 'a' ค่าสองครั้ง ในขณะที่พิมพ์ "{:. 2f} {: .2f}". รูปแบบ (a, b) จะส่งออกค่า 'a' และ 'b'
Alexey Antonenko

2
สำหรับ Python 3 คุณเพียงแค่ต้องเพิ่มเครื่องหมายวงเล็บ (... ) และภายในพวกเขาทั้งหมดที่ฉันเขียนนั้นถูกต้อง
Alexey Antonenko

"ฉันหมายถึงพิมพ์" {0: .2f} {0: .2f} ". รูปแบบ (a, b) จะนำไปสู่ข้อผิดพลาดในผลลัพธ์" อา. นั่นเป็นคำสั่งที่แตกต่างออกไป! บางทีคุณควรแก้ไขคำตอบของคุณ? (ยกตัวอย่างเช่น "error error" หมายถึงอะไรในคำตอบปัจจุบันคุณสามารถยกตัวอย่างของกรณีที่ข้อความที่สองยกข้อยกเว้น แต่ข้อแรกไม่ได้)
Mark Dickinson

3
คุณจะอยู่หลังการพิมพ์ ("{0: .2f} {1: .2f}". รูปแบบ (a, b)) ถ้าคุณมีสองตัวแปร
Hovo

95

ตัวเลขส่วนใหญ่ไม่สามารถแสดงด้วยการลอยได้ ถ้าคุณต้องการปัดเศษตัวเลขเพราะนั่นคือสิ่งที่สูตรทางคณิตศาสตร์หรืออัลกอริทึมของคุณต้องการดังนั้นคุณต้องการใช้การปัดเศษ หากคุณต้องการ จำกัด การแสดงผลให้มีความแม่นยำแน่นอนอย่าใช้รอบและจัดรูปแบบเป็นสตริงนั้น (หากคุณต้องการแสดงด้วยวิธีการปัดเศษแบบอื่นและมีตันคุณต้องผสมสองวิธีเข้าด้วยกัน)

>>> "%.2f" % 3.14159
'3.14'
>>> "%.2f" % 13.9499999
'13.95'

และท้ายที่สุดแม้ว่าที่สำคัญที่สุดคือถ้าคุณต้องการคณิตศาสตร์ที่แน่นอนคุณก็ไม่ต้องการลอยเลย ตัวอย่างปกติคือการจัดการกับเงินและเพื่อเก็บ 'เซ็นต์' เป็นจำนวนเต็ม


68

ลองรหัสด้านล่าง:

>>> a = 0.99334
>>> a = int((a * 100) + 0.5) / 100.0 # Adding 0.5 rounds it up
>>> print a
0.99

แต่พึงระวังมูลค่าของ a ยังคงเป็นค่าที่ไม่แน่นอน ดูที่นี่ - repl.it/LJs (คลิก "เรียกใช้เซสชัน" ที่ด้านบนของส่วนด้านขวา)
ชูชีพ

3
หากคุณใช้วิธีนี้คุณควรเพิ่ม 0.5 เพื่อให้ได้ภาพที่แม่นยำยิ่งขึ้น int (a * 100 + 0.5) / 100.0; การใช้ math.ceil เป็นอีกทางเลือกหนึ่ง
arhuaco

3
@ShashankSawant: สำหรับสิ่งหนึ่งคำตอบที่นำเสนอไม่ได้กลมมันตัดทอน คำแนะนำในการเพิ่มครึ่งหนึ่งเมื่อสิ้นสุดจะปัดเศษ แต่ก็ไม่มีประโยชน์ที่จะทำเช่นนี้เพียงแค่ใช้roundฟังก์ชันในตอนแรก อีกวิธีหนึ่งเนื่องจากโซลูชันนี้ยังคงใช้จุดลอยตัวปัญหาเดิมของ OP ยังคงอยู่แม้ว่าจะเป็น "การแก้ไข" ของโซลูชัน "นี้"
John Y

3
-1 นี่เป็นเพียงการนำroundฟังก์ชันมาใช้ใหม่โดยไม่จำเป็น(ซึ่งถูกใช้ในคำถาม)
ระหว่าง

4
@interjay ซึ่งเป็นสิ่งที่จำเป็นหากใช้round()ไม่ได้ตามที่ OP กล่าวถึง
Pithikos

57

TLDR;)

ปัญหาการปัดเศษเข้า / ส่งออกได้รับการแก้ไขได้อย่างแน่นอนโดย Python 2.7.0และ3.1

ตัวเลขที่ถูกปัดเศษอย่างถูกต้องสามารถแปลงกลับไปกลับมา
str -> float() -> repr() -> float() ...หรือDecimal -> float -> str -> Decimal
ประเภททศนิยมไม่จำเป็นสำหรับการจัดเก็บอีกต่อไป


(ธรรมชาติก็สามารถมีความจำเป็นที่จะรอบผลมาจากการเพิ่มหรือลบหมายเลขที่โค้งมนเพื่อขจัดข้อผิดพลาดบิตสุดท้ายสะสม. เลขคณิตสิบอย่างชัดเจนสามารถยังคงมีประโยชน์ แต่การแปลงสตริงโดยstr()(นั่นคือมีการปัดเศษถึง 12 ตัวเลขที่ถูกต้อง ) ปกติแล้วจะดีพอหากไม่ต้องการความแม่นยำสูงหรือไม่ต้องใช้การคำนวณทางคณิตศาสตร์จำนวนมาก ๆ )

การทดสอบที่ไม่มีที่สิ้นสุด :

import random
from decimal import Decimal
for x in iter(random.random, None):           # Verify FOREVER that rounding is fixed :-)
    assert float(repr(x)) == x                # Reversible repr() conversion.
    assert float(Decimal(repr(x))) == x
    assert len(repr(round(x, 10))) <= 12      # Smart decimal places in repr() after round.
    if x >= 0.1:                              # Implicit rounding to 12 significant digits
        assert str(x) == repr(round(x, 12))   # by str() is good enough for small errors.
        y = 1000 * x                             # Decimal type is excessive for shopping
        assert str(y) == repr(round(y, 12 - 3))  # in a supermaket with Python 2.7+ :-)

เอกสาร

ดูบันทึกประจำรุ่น Python 2.7 - ภาษาอื่นเปลี่ยนย่อหน้าที่สี่:

การแปลงระหว่างตัวเลขทศนิยมและสตริงนั้นถูกปัดเศษอย่างถูกต้องบนแพลตฟอร์มส่วนใหญ่ การแปลงเหล่านี้เกิดขึ้นในหลาย ๆ แห่ง: str () บนจำนวนลอยและจำนวนเชิงซ้อน ตัวสร้างลอยและซับซ้อน การจัดรูปแบบตัวเลข การทำให้เป็นอนุกรมและการทำให้เป็นอนุกรมลอยและหมายเลขที่ซับซ้อนโดยใช้marshal, pickleและjsonโมดูล; การแยกลอยและตัวอักษรในจินตนาการในรหัสหลาม; และการแปลงทศนิยมเป็นทศนิยม

เกี่ยวข้องกับสิ่งนี้การrepr ()ของตัวเลข floating-x จะส่งคืนผลลัพธ์โดยอิงจากสตริงทศนิยมที่สั้นที่สุดที่รับประกันว่าจะปัดกลับเป็น xภายใต้การปัดเศษที่ถูกต้อง (พร้อมโหมดการปัดเศษ ก่อนหน้านี้มันให้สตริงตามการปัดเศษ x ถึง 17 หลักทศนิยม

ปัญหาที่เกี่ยวข้อง


ข้อมูลเพิ่มเติม:จัดรูปแบบของfloatก่อนหลาม 2.7 numpy.float64ใกล้เคียงกับปัจจุบัน ทั้งสองชนิดใช้ความแม่นยำสองเท่า64 บิตIEEE 754 ที่มี 52 mantissa ความแตกต่างใหญ่คือnp.float64.__repr__มีการจัดรูปแบบบ่อยครั้งด้วยจำนวนทศนิยมที่มากเกินไปเพื่อไม่ให้บิตหายไปได้ แต่ไม่มีหมายเลข IEEE 754 ที่ถูกต้องอยู่ระหว่าง 13.94999999999999999 และ 13.950000000000001 ผลลัพธ์ไม่ดีและการแปลงrepr(float(number_as_string))ไม่สามารถย้อนกลับได้ด้วย numpy ในทางกลับกัน:float.__repr__มีการจัดรูปแบบเพื่อให้ทุกหลักมีความสำคัญ; ลำดับนั้นไม่มีช่องว่างและการแปลงกลับได้ ง่ายๆ: ถ้าคุณอาจมีหมายเลข numpy.float64 ให้แปลงเป็นทศนิยมปกติเพื่อจัดรูปแบบสำหรับมนุษย์ไม่ใช่สำหรับโปรเซสเซอร์ที่เป็นตัวเลขมิฉะนั้นไม่จำเป็นต้องใช้ Python 2.7+ อีกต่อไป


ทำไม downvote คำถามคือเกี่ยวกับ Python float(ความแม่นยำสองเท่า) และปกติroundไม่ใช่เกี่ยวกับ numpy.double และการแปลงเป็นสตริง การปัดเศษ Python ธรรมดาไม่สามารถทำได้ดีกว่าใน Python 2.7 คำตอบส่วนใหญ่เขียนไว้ก่อน 2.7 แต่พวกเขาล้าสมัยแม้ว่าพวกเขาจะดีมากในตอนแรก นี่คือเหตุผลของคำตอบของฉัน
hynekcer

53 บิตเมื่อคุณรวม "บิตซ่อน" ซึ่งโดยปริยาย1ยกเว้นในระหว่าง "ค่อยเป็นค่อยไปอันเดอร์โฟลว์"
Rick James

ไม่ใช่ความผิดของกลม แต่เป็นความผิดของหน้าจอ
Rick James

ใช่มันเป็นที่รู้จักกันดี ฉันคิดถึง แต่บริบทหากคุณคัดค้านบางสิ่งใน Python 2.7 บันทึกย่อประจำรุ่นหรือในข้อความของฉันหรือไม่มีอะไรเลย มันซับซ้อนกว่าความจำเป็นในวัตถุประสงค์ของคำถามนี้ มันควรจะเสริมว่านอกจากนี้ยังมีการแปลงจากสตริงจะลอยได้รับการแก้ไขในหลาม 2.7 เนื่องจากการปัดเศษข้อผิดพลาดใน 32 บิตชิป Intel บางอย่างและว่า "รอบ () ฟังก์ชั่นนี้ยังมีในขณะนี้ที่โค้งมนอย่างถูกต้อง." ( บันทึกประจำรุ่น - คุณสมบัติ 3.1 ย้อนกลับไปที่ 2.7 ) คุณเห็นด้วยไหม
hynekcer

1
อ๊ะนั่นคือVSa*b b*aขอบคุณสำหรับลิงค์ - Nostalgia
Rick James

52

ด้วย Python <3 (เช่น 2.6 หรือ 2.7) มีสองวิธีในการทำเช่นนั้น

# Option one 
older_method_string = "%.9f" % numvar

# Option two (note ':' before the '.9f')
newer_method_string = "{:.9f}".format(numvar)

แต่ทราบว่าสำหรับงูหลามรุ่นข้างต้น 3 (เช่น 3.2 หรือ 3.3) ตัวเลือกที่สองคือการแนะนำ

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับตัวเลือกที่สองผมขอแนะนำลิงค์นี้ในการจัดรูปแบบสตริงจากเอกสารหลาม

และสำหรับข้อมูลเพิ่มเติมเกี่ยวกับตัวเลือกหนึ่งที่ลิงค์นี้จะพอเพียงและมีข้อมูลเกี่ยวกับธงต่างๆ

การอ้างอิง: แปลงหมายเลขทศนิยมเป็นความแม่นยำแน่นอนแล้วคัดลอกไปยังสตริง


คุณเป็นตัวแทนของจำนวนเต็มได้อย่างไร ถ้าฉันใช้ "{i3}". format (numvar) ฉันพบข้อผิดพลาด
skytux

นี่คือสิ่งที่ผมหมายถึง: ถ้าnumvar=12.456แล้ว"{:.2f}".format(numvar)ผลตอบแทนถัวเฉลี่ย12.46แต่ให้ข้อผิดพลาดและฉันคาดหวัง"{:2i}".format(numvar) 12
skytux


47

ดูเหมือนจะไม่มีใครพูดถึงเลยดังนั้นขอยกตัวอย่างในรูปแบบ f-string / template-string ของ Python 3.6 ซึ่งฉันคิดว่าเรียบร้อยดี:

>>> f'{a:.2f}'

มันทำงานได้ดีกับตัวอย่างที่ยาวขึ้นด้วยตัวดำเนินการและไม่ต้องการ parens:

>>> print(f'Completed in {time.time() - start:.2f}s')

39

คุณสามารถใช้ตัวดำเนินการรูปแบบสำหรับปัดเศษค่าทศนิยมสูงสุด 2 ตำแหน่งในไพ ธ อน:

print(format(14.4499923, '.2f')) // output is 14.45

4
สตริงส่งคืนนี้
Jemshit Iskenderov

29

ใน Python 2.7:

a = 13.949999999999999
output = float("%0.2f"%a)
print output

1
มันไม่ได้ช่วยอะไรเลย outputมีเดียวกันแน่นอนคุ้มค่าเป็นaดังนั้นคุณอาจรวมทั้งได้เขียนprint aแทนprint outputในบรรทัดสุดท้าย
Mark Dickinson

@MarkDickinson คุณช่วยลองอีกครั้งได้ไหม เพราะมันทำงานได้ตามที่คาดไว้ในคอมไพเลอร์ของฉัน
Shashank Singh

1
คุณพลาดจุดของฉัน ใช่รหัสของคุณพิมพ์13.95ออกมา แต่print aสำหรับค่าเฉพาะนี้aใน Python 2.7 ดังนั้นจึงไม่ชัดเจนว่าจุดของขั้นตอนการจัดรูปแบบคืออะไร
Mark Dickinson

@ MarkDickinson ฉันได้แก้ไขรหัสแล้ว ฉันยอมรับว่า 'print a' พิมพ์ค่าเดียวกันกับ "print output" แต่ถ้าคุณเปรียบเทียบ "a == เอาต์พุต" ผลลัพธ์จะเป็น "False" เนื่องจากขั้นตอนการจัดรูปแบบจะปัดเศษทศนิยม "a" เป็นทศนิยมสองตำแหน่ง
Shashank Singh

1
คุณลองa == outputรหัสที่คุณแสดงหรือไม่ มันให้Trueสำหรับฉันและฉันสงสัยว่ามันจะทำเพื่อคุณเช่นกัน
Mark Dickinson

22

งูหลามกวดวิชามีภาคผนวกที่เรียกว่าFloating Point เลขคณิต: ปัญหาและข้อ จำกัด อ่านมัน มันอธิบายสิ่งที่เกิดขึ้นและทำไม Python จึงพยายามทำให้ดีที่สุด มันยังมีตัวอย่างที่ตรงกับคุณ ให้ฉันพูดเล็กน้อย:

>>> 0.1
0.10000000000000001

คุณอาจถูกล่อลวงให้ใช้round() ฟังก์ชั่นเพื่อตัดกลับเป็นตัวเลขหลักเดียวที่คุณคาดหวัง แต่นั่นก็ไม่ได้ทำให้แตกต่าง:

>>> round(0.1, 1)
0.10000000000000001

ปัญหาคือค่าเลขฐานสองแบบลอยตัวที่เก็บไว้“0.1” นั้นเป็นค่าเลขฐานสองที่ดีที่สุดเท่าที่จะเป็นไปได้1/10ดังนั้นการพยายามปัดมันอีกครั้งไม่สามารถทำให้ดีขึ้นได้: มันดีพอ ๆ กับที่ได้รับ

ผลที่ตามมาอีกประการหนึ่งคือเนื่องจาก0.1 ไม่แน่นอนการ1/10รวมสิบค่าของ0.1อาจไม่ได้ผลอย่างแน่นอน 1.0เช่น:

>>> sum = 0.0
>>> for i in range(10):
...     sum += 0.1
...
>>> sum
0.99999999999999989

ทางเลือกหนึ่งและวิธีแก้ปัญหาของคุณคือการใช้decimalโมดูล




11

ใช้การรวมกันของวัตถุทศนิยมและวิธีการกลม ()

Python 3.7.3
>>> from decimal import Decimal
>>> d1 = Decimal (13.949999999999999) # define a Decimal
>>> d1 
Decimal('13.949999999999999289457264239899814128875732421875')
>>> d2 = round(d1, 2) # round to 2 decimals
>>> d2
Decimal('13.95')

7

สำหรับการแก้ไขจุดลอยตัวในภาษาแบบไดนามิกเช่น Python และ JavaScript ฉันใช้เทคนิคนี้

# For example:
a = 70000
b = 0.14
c = a * b

print c # Prints 980.0000000002
# Try to fix
c = int(c * 10000)/100000
print c # Prints 980

คุณยังสามารถใช้ทศนิยมได้ดังต่อไปนี้:

from decimal import *
getcontext().prec = 6
Decimal(1) / Decimal(7)
# Results in 6 precision -> Decimal('0.142857')

getcontext().prec = 28
Decimal(1) / Decimal(7)
# Results in 28 precision -> Decimal('0.1428571428571428571428571429')

1
getcontext().prec = 6ใช้ได้กับขอบเขตของฟังก์ชั่นหรือสถานที่ทั้งหมดหรือไม่
Julio Marins

1
บริบทเป็นสภาพแวดล้อมสำหรับการดำเนินการทางคณิตศาสตร์ ควบคุมความแม่นยำตั้งกฎสำหรับการปัดเศษพิจารณาว่าสัญญาณใดที่ถือว่าเป็นข้อยกเว้นและ จำกัด ช่วงสำหรับการยกกำลัง แต่ละเธรดมีบริบทปัจจุบันของตนเอง @JulioMarins
Siamand

7
from decimal import Decimal


def round_float(v, ndigits=2, rt_str=False):
    d = Decimal(v)
    v_str = ("{0:.%sf}" % ndigits).format(round(d, ndigits))
    if rt_str:
        return v_str
    return Decimal(v_str)

ผล:

Python 3.6.1 (default, Dec 11 2018, 17:41:10)
>>> round_float(3.1415926)
Decimal('3.14')
>>> round_float(3.1445926)
Decimal('3.14')
>>> round_float(3.1455926)
Decimal('3.15')
>>> round_float(3.1455926, rt_str=True)
'3.15'
>>> str(round_float(3.1455926))
'3.15'


5

ฟังก์ชั่นแลมบ์ดาเป็นเช่นนี้:

arred = lambda x,n : x*(10**n)//1/(10**n)

วิธีนี้คุณสามารถทำได้:

arred(3.141591657,2)

และรับ

3.14

4

ง่ายเหมือน 1,2,3:

  1. ใช้โมดูลทศนิยมสำหรับการคำนวณทศนิยมทศนิยมอย่างรวดเร็วอย่างถูกต้อง:

    d = ทศนิยม (10000000.0000009)

เพื่อให้บรรลุการปัดเศษ:

   d.quantize(Decimal('0.01'))

จะได้ผลลัพธ์ด้วย Decimal('10000000.00')

  1. ทำให้เหนือแห้ง:
    def round_decimal(number, exponent='0.01'):
        decimal_value = Decimal(number)
        return decimal_value.quantize(Decimal(exponent))

หรือ

    def round_decimal(number, decimal_places=2):
        decimal_value = Decimal(number)
        return decimal_value.quantize(Decimal(10) ** -decimal_places)
  1. โหวตขึ้นคำตอบนี้ :)

PS: คำติชมของคนอื่น ๆ : การจัดรูปแบบไม่ได้ปัดเศษ


2

วิธีปัดเศษตัวเลขเป็นความละเอียดวิธีที่ดีที่สุดคือสิ่งต่อไปนี้ซึ่งสามารถทำงานได้กับความละเอียดใด ๆ (0.01 สำหรับสองทศนิยมหรือขั้นตอนอื่น ๆ ):

>>> import numpy as np
>>> value = 13.949999999999999
>>> resolution = 0.01
>>> newValue = int(np.round(value/resolution))*resolution
>>> print newValue
13.95

>>> resolution = 0.5
>>> newValue = int(np.round(value/resolution))*resolution
>>> print newValue
14.0

ใช้งานไม่ได้กับฉันใน python 3.4.3 และ numpy 1.9.1 ใช่ไหม >>> นำเข้า numpy as np >>> res = 0.01 >>> value = 0.184 >>> np.round (ค่า / res) * res 0.1799999999999999999
szeitlin

1
กำลังมองหาเอกสารฉันเห็นว่าปัญหามาจากnumpy.roundความถูกต้อง / ความแม่นยำ ดังนั้นจึงจำเป็นต้องกำหนดว่าเป็น int ก่อนการคูณด้วยความละเอียด ฉันอัปเดตรหัสแล้ว ขอบคุณสำหรับสิ่งนั้น!
iblasi

ที่จำเป็นเท่านั้นคือการแปลงnumpy.float64ผลมาจากการ np.round หรือเพียงเพื่อการใช้งานfloat round(value, 2)ไม่มีหมายเลข IEEE 754 ที่ถูกต้องอยู่ระหว่าง 13.949999999999999 (= 1395 / 100. ) และ 3.950000000000001 (= 1395 * .01) ทำไมคุณคิดว่าวิธีการของคุณดีที่สุด ค่าดั้งเดิม 13.94999999999999999289 (= value = round (value, 2)) มีความแม่นยำมากกว่าของคุณ 13.95000000000000178 (พิมพ์โดย np.float96) ข้อมูลเพิ่มเติมสำหรับ numpy ตอนนี้เพิ่มลงในคำตอบของฉันที่คุณอาจ downvoted โดยไม่ได้ตั้งใจ เดิมทีมันไม่ได้เกี่ยวกับอะไร
hynekcer

@ hynekcer ฉันไม่คิดว่าคำตอบของฉันดีที่สุด แค่ต้องการเพิ่มตัวอย่างของ limit float ไปที่ n ทศนิยม แต่ใกล้เคียงที่สุดของความละเอียดที่กำหนด ฉันตรวจสอบตามที่คุณพูดว่าแทนที่จะintใช้floatสำหรับตัวอย่าง @szeitlin ขอบคุณสำหรับความคิดเห็นเพิ่มเติม (ขออภัย แต่ฉันไม่ได้ลงคะแนนให้คุณ)
iblasi

การเพิ่มการพึ่งพาใหม่ทั้งหมดสำหรับการประมวลผลตัวเลข (pandas) เป็น "วิธีที่ดีที่สุด" หรือไม่
Hejazzman


-13

วิธีที่ฉันใช้คือการแบ่งสตริง มันค่อนข้างง่ายและรวดเร็ว

ก่อนอื่นให้แปลงทศนิยมเป็นสตริงเลือกความยาวที่คุณต้องการ

float = str(float)[:5]

ในบรรทัดเดียวด้านบนเราได้แปลงค่าเป็นสตริงจากนั้นเก็บสตริงเป็นตัวเลขหรือตัวอักษรสี่ตัวแรกเท่านั้น (รวมอยู่ด้วย)

หวังว่าจะช่วย!


2
โปรดอย่าโพสต์คำตอบที่เหมือนกันสำหรับคำถามหลายข้อ
vaultah

18
ว้าว ... tdh ... โปรดอย่าทำซอฟต์แวร์บัญชี ... จะเกิดอะไรขึ้นถ้าจำนวนนั้นเป็น 113.94? นี้จะส่งผลใน 113.9 ... ปล่อย 0.04 หายไป .... นอกจากนี้ยังมีคำตอบจากกว่า 5 ปีที่ผ่านมา ....
Angry 84
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.