ขอ اعتراف: ผมชอบ floating-point numbers ไม่ค่อยได้ แน่นอนมัน useful ถ้าคุณเป็นคอมพิวเตอร์ แต่ถ้าคุณเป็นมนุษย์ จะ scratching head กับสถานการณ์แบบนี้:
129.95 * 100 # => 12994.999999999998
ไม่ได้แค่ขัดต่อ mathematical harmony แต่วิ่ง UX ด้วย
ถ้าสูตรอาหารบอก measure 0.37211927843 ถ้วยแป้ง คุณน่าจะหัวเราะว่า author ไอ้โง่ แล้ววัด 1/3 ถ้วยแทน
คนส่วนใหญ่คิด fractions ได้ง่ายกว่า arbitrary decimal numbers ถ้าแอปพยายาม communicate numbers ให้คน อาจควร express เป็น fractions
คลาส Rational ของ Ruby เครื่องมือยอดเยี่ยมสำหรับ rational numbers ไม่ได้แค่ rational math แตelh หา simple fractions ที่ approximate floating-point numbers ที่ยุ่งยากได้ มาดู!
Rationals คืออะไร?
เพื่อวัตถุประสงค์ของเรา "rational numbers" คือคำ fancy สำหรับ "fractions" มีสองส่วน: numerator และ denominator
1/2 # Numerator is 1. Denominator is 2.
5 # Numerator is 5. Denominator is 1.
ใน Ruby rational numbers มี data type ของตัวเองเหมือน integers และ floating-point numbers มีหลายวิธีสร้าง rational ใหม่:
3/2r # syntax นี้ introduced ใน Ruby 2.1
1.5.to_r # Floats แปลงเป็น rationals ผ่าน `to_r`
"3/2".to_r # strings ก็ได้
Rational('3/2') # วิธีเก่า
Rational(3, 2) # ดูว่าชีวิตยากแค่ไหน
คณิตศาสตร์พื้นฐาน
บวก ลบ คูณ หาร rational numbers สองตัว ผลลัพธ์ก็เป็น rational
2/3r + 1/3r # => (1/1)
2/3r - 1/3r # => (1/3)
2/3r * 1/3r # => (2/9)
(2/3r) / (1/3r) # ต้องมี parens เพื่อไม่ให้ interpreter หงุดหงิด
# => (2/1)
Operators อื่นๆ ก็ทำงานเหมือนคาดการณ์: **, >, < ฯลฯ
กฎทั่วไป: inputs ทั้งสองต้องเป็น fractions ผลลัพธ์ถึงเป็น fraction ข้อยกเว้นคือ integers เนื่องจาก integers ทั้งหมดเป็น rational Ruby ทำอย่างฉลาด assume rational output:
2/3r + 2 # => (8/3)
การประมาณค่า
ประโยชน์มากของ rational numbers: approximate และคำนวณในหัวได้ง่าย ต้องเก็บ fractions ให้ simple 3/2 แทน 3320774221237909/2251799813685248
โชคดีที่ Ruby มีวิธีง่ายแปลง precise-but-ugly numbers เป็น approximate-yet-pretty numbers เมธอด rationalize
รูปแบบ:
# Precise but ugly
(1.47472).to_r
# => (3320774221237909/2251799813685248)
# Less precise, but pretty
(1.47472).to_r.rationalize(0.05)
# => (3/2)
rationalize มี argument หนึ่ง ระบุ tolerance - ความแม่นยำที่ยอม trade สำหรับ simplicity
Method หา number ที่ denominator ต่ำสุดภายใน tolerance ของคุณ:
# Number ที่ denominator ต่ำสุดระหว่าง 5/10 และ 7/10?
(6/10r).rationalize(1/10r) # => (1/2)
# ระหว่าง 11/20 และ 13/20?
(6/10r).rationalize(1/20r) # => (3/5)
# ..และระหว่าง 1/10 และ 11/10?
(6/10r).rationalize(1/2r) # => (1/1)
Cruder approximations
ถ้าต้องการแค่ integer หรือ floating-point number ที่ตรงกับ fraction มีหลายตัวเลือก:
# คืน nearest integer
(6/10r).round # => 1
# Round down
(12/10r).to_i # => 1
Limitations ของ Rationals
มี limitations ตอนทำงานกับ rational numbers และ Ruby ไม่สามารถหารศูนย์ได้:
4/0r
ZeroDivisionError: divided by 0
และเจอพฤติกรรมแปลกถ้าพยายาม treat irrational numbers เป็น rational:
# Umm, square root of 2 ไม่ใช่ irrational ไหม?
Rational(Math.sqrt(2))
# => (6369051672525773/4503599627370496)
# และ PI เป็น irrational เช่นกัน
Rational(Math::PI)
# => (884279719003555/281474976710656)
คาดว่าจะ raise exception แต่ Ruby ไม่พอ smart พอ แปลง floating-point approximations ของ irrational numbers เป็น rationals แทน ไม่ใช่ปัญหาใหญ่ แต่ควรทราบ