เป็นวิธีที่ดีที่สุดในการเปรียบเทียบลอยสำหรับความเท่าเทียมกันเกือบในงูหลามคืออะไร?


331

เป็นที่ทราบกันดีว่าการเปรียบเทียบลอยเพื่อความเท่าเทียมนั้นมีปัญหาเล็กน้อยเนื่องจากปัญหาการปัดเศษและความแม่นยำ

ตัวอย่างเช่น: https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/

วิธีที่แนะนำในการจัดการกับสิ่งนี้ใน Python คืออะไร?

มีฟังก์ชั่นห้องสมุดมาตรฐานสำหรับที่นี่ไหม?


@tolomea: ขึ้นอยู่กับแอปพลิเคชันของคุณและข้อมูลและโดเมนปัญหาของคุณ - และเป็นรหัสบรรทัดเดียว - เหตุใดจึงมี "ฟังก์ชันไลบรารีมาตรฐาน"
S.Lott

9
@ S.Lott: all, any, max, minแต่ละพื้นหนึ่งสมุทรและพวกเขาจะไม่ได้ให้เพียงแค่ในห้องสมุดที่พวกเขากำลังฟังก์ชั่นในตัว ดังนั้นเหตุผลของ BDFL ก็ไม่ได้เป็นเช่นนั้น โค้ดหนึ่งบรรทัดที่คนส่วนใหญ่เขียนนั้นไม่ค่อยซับซ้อนและมักจะไม่ทำงานซึ่งเป็นเหตุผลที่ดีที่จะให้สิ่งที่ดีกว่า แน่นอนว่าโมดูลใด ๆ ที่ให้กลยุทธ์อื่น ๆ จะต้องให้คำอธิบายที่เหมาะสมเมื่อพวกมันเหมาะสมและที่สำคัญกว่านั้นคือ การวิเคราะห์เชิงตัวเลขเป็นเรื่องยากไม่มีความละอายที่นักออกแบบภาษามักจะไม่พยายามใช้เครื่องมือเพื่อช่วย
Steve Jessop

@ Steve Jessop ฟังก์ชันที่มุ่งเน้นการรวบรวมเหล่านั้นไม่มีแอปพลิเคชันข้อมูลและการพึ่งพาโดเมนปัญหาที่เกิดจากการใช้จุดลอย ดังนั้น "หนึ่งซับ" ชัดเจนไม่สำคัญเท่าเหตุผลจริง การวิเคราะห์เชิงตัวเลขนั้นยากและไม่สามารถเป็นส่วนหนึ่งของห้องสมุดภาษาทั่วไปได้
S.Lott

6
@ S.Lott: ฉันอาจเห็นด้วยว่าการแจกจ่าย Python มาตรฐานไม่ได้มาพร้อมกับโมดูลหลายตัวสำหรับอินเตอร์เฟส XML เห็นได้ชัดว่าความจริงที่ว่าแอพพลิเคชั่นที่แตกต่างกันต้องทำอะไรที่แตกต่างกันคือไม่มีบาร์เลยที่จะวางโมดูลในชุดพื้นฐานเพื่อทำมันไม่ทางใดก็ทางหนึ่ง แน่นอนว่ามีเทคนิคในการเปรียบเทียบการลอยที่นำมาใช้ใหม่จำนวนมากขั้นพื้นฐานที่สุดคือจำนวน ulps ที่ระบุ ดังนั้นฉันจึงเห็นด้วยเพียงบางส่วนปัญหาคือการวิเคราะห์เชิงตัวเลขนั้นยาก โดยหลักการแล้วPython สามารถใช้เป็นเครื่องมือในการทำให้ง่ายขึ้นบางเวลา ฉันเดาว่าไม่มีใครอาสา
Steve Jessop

4
ยิ่งไปกว่านั้น "มันทำให้โค้ดที่ยากต่อการออกแบบหนึ่งบรรทัด" - ถ้ามันยังเป็นซับในเมื่อคุณทำอย่างถูกต้องแล้วฉันคิดว่าจอภาพของคุณกว้างกว่าของฉัน ;-) อย่างไรก็ตามฉันคิดว่าพื้นที่ทั้งหมดค่อนข้างพิเศษในแง่ที่โปรแกรมเมอร์ส่วนใหญ่ (รวมถึงฉัน) ไม่ค่อยได้ใช้มัน เมื่อรวมเข้ากับความยากลำบากมันจะไม่ไปอยู่อันดับต้น ๆ ของรายการ "ที่ต้องการมากที่สุด" สำหรับไลบรารีหลักในภาษาส่วนใหญ่
Steve Jessop

คำตอบ:


324

งูหลาม 3.5 เพิ่มmath.iscloseและcmath.iscloseฟังก์ชั่นที่อธิบายไว้ในPEP 485

หากคุณกำลังใช้รุ่นก่อนหน้าของงูหลาม, ฟังก์ชั่นเทียบเท่าจะได้รับในเอกสาร

def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
    return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)

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

abs_tolเป็นความอดทนแบบสัมบูรณ์ที่ถูกใช้ตามที่เป็นอยู่ในทุกกรณี หากความแตกต่างน้อยกว่าความคลาดเคลื่อนทั้งสองค่าจะถือว่าเท่ากัน


26
ทราบเมื่อaหรือbเป็นnumpy array, numpy.iscloseงาน
dbliss

6
@ มาร์ชrel_tolเป็นความอดทนที่สัมพันธ์กันมันทวีคูณด้วยขนาดที่ใหญ่กว่าของการโต้แย้งทั้งสอง เมื่อค่ามีขนาดใหญ่ขึ้นดังนั้นความแตกต่างที่ได้รับอนุญาตระหว่างที่ยังคงพิจารณาพวกเขาเท่ากัน abs_tolเป็นความอดทนแบบสัมบูรณ์ที่ถูกใช้ตามที่เป็นอยู่ในทุกกรณี หากความแตกต่างน้อยกว่าความคลาดเคลื่อนทั้งสองค่าจะถือว่าเท่ากัน
Mark Ransom

5
ที่จะไม่ลดคุณค่าของคำตอบนี้ (ฉันคิดว่ามันเป็นสิ่งที่ดี) มันก็น่าสังเกตว่าเอกสารยังบอกว่า: "การตรวจสอบข้อผิดพลาด Modulo ฯลฯ ฟังก์ชั่นจะส่งกลับผลลัพธ์ของ ... " ในคำอื่น ๆiscloseฟังก์ชั่น (ด้านบน) ไม่ใช่การใช้งานที่สมบูรณ์
rkersh

5
ขอโทษสำหรับการฟื้นฟูเธรดเก่า แต่มันก็คุ้มค่าที่จะชี้ให้เห็นว่าเป็นiscloseไปตามเกณฑ์ที่อนุรักษ์นิยมน้อยกว่าเสมอ ฉันพูดถึงเพียงเพราะพฤติกรรมนั้นขัดกับฉัน หากฉันระบุเกณฑ์สองเกณฑ์ฉันจะคาดหวังว่าค่าเผื่อที่น้อยกว่าจะยิ่งใหญ่กว่านั้น
Mackie Messer

3
@MackieMesser คุณมีสิทธิ์ได้รับความเห็นของคุณแน่นอน แต่พฤติกรรมนี้เหมาะสมกับฉัน ตามคำจำกัดความของคุณคงไม่มีอะไรที่จะ "เป็นศูนย์" เพราะความอดทนสัมพัทธ์คูณด้วยศูนย์จะเป็นศูนย์เสมอ
Mark Ransom

72

สิ่งที่เรียบง่ายดังต่อไปนี้ไม่ดีพอหรือไม่?

return abs(f1 - f2) <= allowed_error

8
เมื่อลิงก์ที่ฉันให้ไว้นั้นการลบจะใช้งานได้ก็ต่อเมื่อคุณทราบขนาดโดยประมาณของตัวเลขล่วงหน้า
Gordon Wrigley

8
abs(f1-f2) < tol*max(abs(f1),abs(f2))จากประสบการณ์ของผมวิธีที่ดีที่สุดสำหรับลอยเปรียบเทียบคือ: ความอดทนสัมพัทธ์ประเภทนี้เป็นวิธีเดียวที่มีความหมายในการเปรียบเทียบลอยโดยทั่วไปเนื่องจากจะได้รับผลกระทบจากข้อผิดพลาดของการปัดเศษในตำแหน่งทศนิยมเล็ก
Sesquipedal

2
เพียงแค่เพิ่มตัวอย่างง่ายๆว่าทำไมมันอาจจะไม่ทำงาน: มันถัวเฉลี่ย>>> abs(0.04 - 0.03) <= 0.01 Falseฉันใช้Python 2.7.10 [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
schatten

3
@schatten ยุติธรรมตัวอย่างนั้นมีส่วนเกี่ยวข้องกับความแม่นยำ / รูปแบบไบนารีของเครื่องมากกว่า algo เปรียบเทียบโดยเฉพาะ เมื่อคุณป้อน 0.03 ลงในระบบนั่นไม่ใช่จำนวนที่ทำกับซีพียูจริงๆ
แอนดรูว์ไวท์

2
@AndrewWhite ตัวอย่างนั้นแสดงว่าabs(f1 - f2) <= allowed_errorไม่ทำงานตามที่คาดไว้
Schatten

45

ฉันยอมรับว่าคำตอบของ Gareth น่าจะเหมาะสมที่สุดกับฟังก์ชั่น / โซลูชันที่มีน้ำหนักเบา

แต่ฉันคิดว่ามันจะมีประโยชน์ที่จะทราบว่าถ้าคุณกำลังใช้ NumPy หรือกำลังพิจารณาอยู่มันมีฟังก์ชั่นแบบแพคเกจสำหรับสิ่งนี้

numpy.isclose(a, b, rtol=1e-05, atol=1e-08, equal_nan=False)

ข้อจำกัดความรับผิดชอบเล็กน้อย: การติดตั้ง NumPy อาจเป็นประสบการณ์ที่ไม่สำคัญขึ้นอยู่กับแพลตฟอร์มของคุณ


1
"การติดตั้ง numpy อาจเป็นประสบการณ์ที่ไม่สำคัญขึ้นอยู่กับแพลตฟอร์มของคุณ" ... เอ่ออะไรนะ? แพลทฟอร์มใดที่ "ไม่สำคัญ" สำหรับการติดตั้งแบบไม่ระบุหมายเลข? อะไรทำให้มันไม่สำคัญเลยเหรอ?
จอห์น

10
@John: ยากที่จะรับไบนารี 64 บิตสำหรับ Windows ยากที่จะเข้าใจผ่านทางpipWindows
Ben Bolker

@Ternak: ฉันทำ แต่นักเรียนของฉันบางคนใช้ Windows ดังนั้นฉันต้องจัดการกับสิ่งนี้
Ben Bolker

4
@BenBolker หากคุณต้องติดตั้งแพลตฟอร์มวิทยาศาสตร์ข้อมูลแบบเปิดที่ขับเคลื่อนโดย Python วิธีที่ดีที่สุดคือ Anaconda continuum.io/downloads (pandas, numpy และอื่น ๆ อีกมากมาย)
jrovegno

การติดตั้ง Anaconda เป็นเรื่องเล็กน้อย
endolith

13

ใช้decimalโมดูลของ Python ซึ่งจัดเตรียมDecimalคลาส

จากความคิดเห็นที่:

เป็นที่น่าสังเกตว่าถ้าคุณกำลังทำคณิตศาสตร์หนักและคุณไม่ต้องการความแม่นยำจากทศนิยมนี่สามารถทำให้สิ่งต่าง ๆ ลงไปได้ ทุ่นลอยน้ำเป็นหนทางวิธีที่เร็วกว่าที่จะจัดการกับ แต่ไม่แน่ชัด ทศนิยมมีความแม่นยำมาก แต่ช้า


11

ฉันไม่ทราบว่ามีอะไรในห้องสมุดมาตรฐาน Python (หรือที่อื่น ๆ ) ที่ใช้งานAlmostEqual2sComplementฟังก์ชั่นของ Dawson หากนั่นเป็นพฤติกรรมที่คุณต้องการคุณจะต้องใช้มันด้วยตัวเอง (ซึ่งในกรณีนี้แทนที่จะใช้ดอว์สันแฮ็บิตฉลาดที่คุณต้องการอาจจะทำได้ดีกว่าที่จะใช้การทดสอบแบบดั้งเดิมมากขึ้นในรูปแบบif abs(a-b) <= eps1*(abs(a)+abs(b)) + eps2หรือคล้ายกันที่จะได้รับดอว์สันเหมือนพฤติกรรมคุณอาจจะบอกว่าสิ่งที่ต้องการ. if abs(a-b) <= eps*max(EPS,abs(a),abs(b))สำหรับบางขนาดเล็กคงEPSนี้ไม่ว่า เช่นเดียวกับดอว์สัน แต่มันก็เหมือนวิญญาณ


ฉันไม่ค่อยติดตามสิ่งที่คุณกำลังทำที่นี่ แต่มันน่าสนใจ ความแตกต่างระหว่าง eps, eps1, eps2 และ EPS คืออะไร?
Gordon Wrigley

eps1และeps2กำหนดเป็นญาติและอดทนแน่นอน: คุณกำลังเตรียมที่จะอนุญาตให้มีaและbจะแตกต่างกันโดยประมาณครั้งใหญ่แค่ไหนบวกeps1 เป็นความอดทนเดียว; คุณกำลังเตรียมที่จะอนุญาตให้มีและจะแตกต่างกันโดยประมาณครั้งใหญ่ว่าพวกเขาจะมีเงื่อนไขที่ว่าอะไรที่มีขนาดเล็กหรือจะถือว่าเป็นขนาด หากคุณใช้ค่าที่ไม่ใช่ค่าต่ำที่สุดของประเภททศนิยมของคุณสิ่งนี้จะคล้ายกับ Dawson's comparator (ยกเว้นตัวประกอบ 2 ^ # บิตเนื่องจาก Dawson วัดค่าความคลาดเคลื่อนใน ulps) eps2epsabepsEPSEPSEPS
Gareth McCaughan

2
บังเอิญฉันเห็นด้วยกับ S. Lott ว่าสิ่งที่ถูกต้องมักจะขึ้นอยู่กับแอปพลิเคชันของคุณซึ่งเป็นสาเหตุที่ไม่มีฟังก์ชั่นไลบรารีมาตรฐานเดียวสำหรับการเปรียบเทียบจุดลอยตัวทั้งหมดของคุณ
Gareth McCaughan

@ gareth-mccaughan คุณกำหนดค่า "non-denormal ที่เล็กที่สุดของประเภททศนิยมของคุณ" สำหรับไพ ธ อนได้อย่างไร?
Gordon Wrigley

หน้านี้docs.python.org/tutorial/floatingpoint.htmlกล่าวว่าเกือบทุกการใช้งานหลามใช้ IEEE-754 ลอยแม่นยำคู่และหน้านี้en.wikipedia.org/wiki/IEEE_754-1985กล่าวว่าตัวเลขปกติที่อยู่ใกล้กับศูนย์มี± 2 * * -1022
Gordon Wrigley

11

ภูมิปัญญาทั่วไปที่ตัวเลขทศนิยมไม่สามารถนำมาเปรียบเทียบเพื่อความเท่าเทียมกันได้ไม่ถูกต้อง จำนวนจุดลอยตัวไม่แตกต่างจากจำนวนเต็ม: ถ้าคุณประเมิน "a == b" คุณจะได้รับจริงถ้าพวกเขาเป็นตัวเลขที่เหมือนกันและเป็นเท็จอย่างอื่น (ด้วยความเข้าใจว่าแน่นอนว่า NaN สองตัวนั้นไม่เหมือนกัน)

ปัญหาที่แท้จริงคือ: ถ้าฉันทำการคำนวณบางอย่างแล้วและไม่แน่ใจว่าตัวเลขสองตัวที่ฉันต้องเปรียบเทียบนั้นถูกต้องตรงไหนแล้วอะไรล่ะ? ปัญหานี้เหมือนกันสำหรับเลขทศนิยมเนื่องจากเป็นจำนวนเต็ม หากคุณประเมินค่านิพจน์จำนวนเต็ม "7/3 * 3" จะไม่เปรียบเทียบเท่ากับ "7 * 3/3"

สมมติว่าเราถามว่า "ฉันจะเปรียบเทียบจำนวนเต็มกับความเท่าเทียมได้อย่างไร" ในสถานการณ์เช่นนี้ ไม่มีคำตอบเดียว; สิ่งที่คุณควรทำขึ้นอยู่กับสถานการณ์ที่เฉพาะเจาะจงโดยเฉพาะอย่างยิ่งข้อผิดพลาดที่คุณมีและสิ่งที่คุณต้องการบรรลุ

นี่เป็นตัวเลือกที่เป็นไปได้

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

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

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

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

ดังนั้นวิธีที่คุณตัดสินใจว่าจะกลับมาอย่างไรเมื่อเปรียบเทียบจำนวนจุดลอยตัวนั้นขึ้นอยู่กับสถานการณ์ของคุณเป็นอย่างมาก

สำหรับวิธีการพิสูจน์ขอบเขตข้อผิดพลาดในการคำนวณซึ่งอาจเป็นเรื่องที่ซับซ้อน การนำไปใช้งานแบบ floating-point ใด ๆ ที่ใช้มาตรฐาน IEEE 754 ในโหมดการปัดเศษไปยังที่ใกล้ที่สุดจะส่งกลับตัวเลข floating-point ที่ใกล้เคียงกับผลลัพธ์ที่แน่นอนที่สุดสำหรับการดำเนินการพื้นฐานใด ๆ (โดยเฉพาะการคูณการหารการบวกการลบรากที่สอง) (ในกรณีที่มีความเสมอกันให้ปัดเศษให้มีค่าบิตต่ำ) (โปรดระมัดระวังเป็นพิเศษเกี่ยวกับสแควร์รูทและการหารการใช้ภาษาของคุณอาจใช้วิธีที่ไม่สอดคล้องกับมาตรฐาน IEEE 754 สำหรับสิ่งเหล่านี้) เนื่องจากข้อกำหนดนี้ ข้อผิดพลาดในผลลัพธ์เดียวมีค่าอย่างน้อยที่สุด 1/2 ของค่าบิตที่มีนัยสำคัญน้อยที่สุด (ถ้ามีมากขึ้นการปัดเศษจะเป็นจำนวนที่ต่างกันซึ่งอยู่ภายใน 1/2 ของค่า)

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

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


3
จากการสนทนาข้างต้นกับ Gareth McCaughan เปรียบเทียบอย่างถูกต้องกับข้อผิดพลาดที่สัมพันธ์กับ "abs (ab) <= eps สูงสุด (2 * -1022, abs (a), abs (b))" นั่นไม่ใช่สิ่งที่ฉันจะอธิบาย เรียบง่ายและไม่ใช่สิ่งที่ฉันจะทำด้วยตัวเอง นอกจากนี้สตีฟเจสซอพชี้ให้เห็นว่ามันมีความซับซ้อนคล้ายกันกับแม็กซ์มินทุก ๆ สิ่งซึ่งเป็นสิ่งที่มีอยู่ภายใน ดังนั้นการให้การเปรียบเทียบข้อผิดพลาดสัมพัทธ์ในโมดูลคณิตศาสตร์มาตรฐานดูเหมือนเป็นความคิดที่ดี
Gordon Wrigley

(7/3 * 3 == 7 * 3/3) หาค่า True ใน python
xApple

@xApple: ฉันเพิ่งวิ่ง Python 2.7.2 บน OS X 10.8.3 (7/3*3 == 7*3/3)และเข้า Falseมันพิมพ์
Eric Postpischil

3
from __future__ import divisionคุณอาจลืมที่จะพิมพ์ หากคุณไม่ทำเช่นนั้นจะไม่มีตัวเลขจุดลอยตัวและการเปรียบเทียบอยู่ระหว่างจำนวนเต็มสองตัว
xApple

3
นี่คือการสนทนาที่สำคัญ แต่ไม่มีประโยชน์อย่างไม่น่าเชื่อ
Dan Hulme

6

หากคุณต้องการใช้ในการทดสอบ / บริบท TDD ฉันจะบอกว่านี่เป็นวิธีมาตรฐาน:

from nose.tools import assert_almost_equals

assert_almost_equals(x, y, places=7) #default is 7

5

math.isclose ()ถูกเพิ่มใน Python 3.5 สำหรับนั้น ( ซอร์สโค้ด ) นี่คือพอร์ตของมันไปยัง Python 2 มันแตกต่างจากหนึ่งซับของ Mark Ransom คือมันสามารถจัดการ "inf" และ "-inf" ได้อย่างถูกต้อง

def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
    '''
    Python 2 implementation of Python 3.5 math.isclose()
    https://hg.python.org/cpython/file/tip/Modules/mathmodule.c#l1993
    '''
    # sanity check on the inputs
    if rel_tol < 0 or abs_tol < 0:
        raise ValueError("tolerances must be non-negative")

    # short circuit exact equality -- needed to catch two infinities of
    # the same sign. And perhaps speeds things up a bit sometimes.
    if a == b:
        return True

    # This catches the case of two infinities of opposite sign, or
    # one infinity and one finite number. Two infinities of opposite
    # sign would otherwise have an infinite relative tolerance.
    # Two infinities of the same sign are caught by the equality check
    # above.
    if math.isinf(a) or math.isinf(b):
        return False

    # now do the regular computation
    # this is essentially the "weak" test from the Boost library
    diff = math.fabs(b - a)
    result = (((diff <= math.fabs(rel_tol * b)) or
               (diff <= math.fabs(rel_tol * a))) or
              (diff <= abs_tol))
    return result

2

ฉันพบว่าการเปรียบเทียบดังต่อไปนี้มีประโยชน์:

str(f1) == str(f2)

เป็นเรื่องที่น่าสนใจ แต่ไม่ค่อยมีประโยชน์เนื่องจาก str (.1 + .2) == .3
Gordon Wrigley

str (.1 + .2) == str (.3) ส่งคืน True
Henrikh Kantuni

สิ่งนี้แตกต่างจาก f1 == f2 อย่างไร - หากทั้งคู่อยู่ใกล้ แต่ก็ยังแตกต่างกันเนื่องจากความแม่นยำการแทนสตริงจะไม่เท่ากัน
MrMas

2
.1 + .2 == .3 คืนค่าเท็จขณะ str (.1 + .2) == str (.3) ส่งคืน True
Kresimir

4
ใน Python 3.7.2 str(.1 + .2) == str(.3)จะส่งคืนค่า False วิธีที่อธิบายไว้ข้างต้นใช้ได้กับ Python 2 เท่านั้น
Danibix

1

สำหรับบางกรณีที่คุณสามารถส่งผลต่อการแทนค่าหมายเลขต้นฉบับคุณสามารถแทนค่าเหล่านี้เป็นเศษส่วนแทนการลอยโดยใช้ตัวเศษเลขและตัวหาร ด้วยวิธีนี้คุณสามารถเปรียบเทียบได้อย่างแม่นยำ

ดูรายละเอียดในส่วนของเศษส่วนจากโมดูลเศษส่วน


1

ฉันชอบคำแนะนำของ @Sesquipedal แต่มีการปรับเปลี่ยน (กรณีการใช้งานพิเศษเมื่อค่าทั้งสองเป็น 0 คืนเท็จ) ในกรณีของฉันฉันใช้ Python 2.7 และเพิ่งใช้ฟังก์ชั่นง่าย ๆ :

if f1 ==0 and f2 == 0:
    return True
else:
    return abs(f1-f2) < tol*max(abs(f1),abs(f2))

1

มีประโยชน์สำหรับกรณีที่คุณต้องการให้แน่ใจว่าตัวเลข 2 ตัวนั้นเหมือนกัน 'ถึงความแม่นยำ' ไม่จำเป็นต้องระบุความอดทน:

  • ค้นหาความแม่นยำขั้นต่ำของตัวเลข 2 ตัว

  • ปัดเศษทั้งสองเป็นความแม่นยำขั้นต่ำและเปรียบเทียบ

def isclose(a,b):                                       
    astr=str(a)                                         
    aprec=len(astr.split('.')[1]) if '.' in astr else 0 
    bstr=str(b)                                         
    bprec=len(bstr.split('.')[1]) if '.' in bstr else 0 
    prec=min(aprec,bprec)                                      
    return round(a,prec)==round(b,prec)                               

ตามที่เขียนไว้ใช้สำหรับตัวเลขที่ไม่มี 'e' ในการแสดงสตริงเท่านั้น (หมายถึง 0.9999999999995e-4 <number <= 0.9999999999995e11)

ตัวอย่าง:

>>> isclose(10.0,10.049)
True
>>> isclose(10.0,10.05)
False

แนวคิดของการปิดที่ไม่ได้ จำกัด นั้นจะไม่ให้บริการคุณดี isclose(1.0, 1.1)ผลิตFalseและผลตอบแทนisclose(0.1, 0.000000000001) True
kfsone

1

วิธีเปรียบเทียบทศนิยมที่กำหนดโดยไม่ต้องatol/rtol:

def almost_equal(a, b, decimal=6):
    return '{0:.{1}f}'.format(a, decimal) == '{0:.{1}f}'.format(b, decimal)

print(almost_equal(0.0, 0.0001, decimal=5)) # False
print(almost_equal(0.0, 0.0001, decimal=4)) # True 

1

นี่อาจเป็นแฮ็คที่น่าเกลียดเล็กน้อย แต่ก็ใช้งานได้ดีเมื่อคุณไม่ต้องการความแม่นยำในการตั้งค่าเริ่มต้นมากขึ้น (ประมาณ 11 ทศนิยม)

round_toฟังก์ชั่นใช้วิธีการรูปแบบจากในตัวชั้น STR รอบขึ้นลอยสตริงที่แสดงถึงลอยที่มีจำนวนของทศนิยมที่จำเป็นและจากนั้นนำไปใช้EVALตัวในการทำงานเพื่อสตริงลอยโค้งมนที่จะได้รับกลับมา จะลอยชนิดตัวเลข

is_closeฟังก์ชั่นใช้เพียงเงื่อนไขง่ายๆที่จะขึ้นลอยโค้งมน

def round_to(float_num, prec):
    return eval("'{:." + str(int(prec)) + "f}'.format(" + str(float_num) + ")")

def is_close(float_a, float_b, prec):
    if round_to(float_a, prec) == round_to(float_b, prec):
        return True
    return False

>>>a = 10.0
10.0
>>>b = 10.0001
10.0001
>>>print is_close(a, b, prec=3)
True
>>>print is_close(a, b, prec=4)
False

ปรับปรุง:

ตามที่ @stepehjfox แนะนำวิธีที่สะอาดกว่าในการสร้างฟังก์ชั่นrount_toเพื่อหลีกเลี่ยง "eval" กำลังใช้การจัดรูปแบบซ้อนกัน :

def round_to(float_num, prec):
    return '{:.{precision}f}'.format(float_num, precision=prec)

การทำตามแนวคิดเดียวกันนี้รหัสสามารถทำได้ง่ายขึ้นโดยใช้f-stringsใหม่ที่ยอดเยี่ยม(Python 3.6+):

def round_to(float_num, prec):
    return f'{float_num:.{prec}f}'

ดังนั้นเราสามารถสรุปได้ทั้งหมดในฟังก์ชั่น'is_close' ที่เรียบง่ายและสะอาดตา :

def is_close(a, b, prec):
    return f'{a:.{prec}f}' == f'{b:.{prec}f}'

1
คุณไม่จำเป็นต้องใช้eval()การจัดรูปแบบที่กำหนดพารามิเตอร์ สิ่งที่ return '{:.{precision}f'.format(float_num, precision=decimal_precision) ควรทำ
stephenjfox

1
แหล่งที่มาสำหรับความคิดเห็นของฉันและตัวอย่างเพิ่มเติม: pyformat.info/#param_align
stephenjfox

1
ขอบคุณ @stephenjfox ฉันไม่รู้เกี่ยวกับการจัดรูปแบบซ้อนกัน Btw โค้ดตัวอย่างของคุณไม่มีวงเล็บปีกกาตอนจบ:return '{:.{precision}}f'.format(float_num, precision=decimal_precision)
Albert Alomar

1
การจับที่ดีและการปรับปรุงที่ทำได้ดีเป็นพิเศษกับ f-strings ด้วยการตายของ Python 2 รอบมุมบางทีนี่อาจจะเป็นบรรทัดฐาน
stephenjfox

0

ในแง่ของข้อผิดพลาดแน่นอนคุณสามารถตรวจสอบได้

if abs(a - b) <= error:
    print("Almost equal")

ข้อมูลบางอย่างเกี่ยวกับสาเหตุที่ทลอยกระทำผิดแปลกใน Python https://youtu.be/v4HhvoNLILk?t=1129

คุณยังสามารถใช้math.iscloseสำหรับข้อผิดพลาดที่เกี่ยวข้อง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.