ทศนิยมเทียบกับทศนิยมใน ActiveRecord


283

บางครั้งประเภทข้อมูล Activerecord ทำให้ฉันสับสน เอ่อบ่อย หนึ่งในคำถามที่นิรันดร์ของฉันคือสำหรับกรณีที่กำหนด

ฉันควรใช้:decimalหรือ:float?

ฉันเคยเจอลิงก์นี้บ่อยครั้งActiveRecord:: ทศนิยม vs: float? แต่คำตอบไม่ชัดเจนพอที่ฉันจะมั่นใจ:

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

นี่คือตัวอย่างบางกรณี:

  • Geolocation / ละติจูด / ลองจิจูด: -45.756688, 120.5777777...
  • อัตราส่วน / ร้อยละ: 0.9, 1.25, 1.333, 1.4143...

ฉันเคยใช้:decimalในอดีต แต่ฉันพบว่าการจัดการกับBigDecimalวัตถุใน Ruby นั้นไม่สะดวกสบายเมื่อเทียบกับการลอย ฉันรู้ว่าฉันสามารถใช้:integerเพื่อเป็นตัวแทนเงิน / เซ็นต์ได้เช่นกัน แต่มันก็ไม่เหมาะสำหรับกรณีอื่น ๆ เช่นเมื่อปริมาณที่ความแม่นยำอาจเปลี่ยนแปลงไปตามกาลเวลา

  • ข้อดี / ข้อเสียของการใช้แต่ละข้อมีอะไรบ้าง
  • มีกฎอะไรบ้างที่จะรู้ว่าควรใช้แบบไหน

คำตอบ:


427

ฉันจำศาสตราจารย์ CompSci ของฉันว่าจะไม่ใช้ลอยเป็นเงิน

สาเหตุที่เป็นเช่นนั้นคุณสมบัติ IEEE กำหนดลอยในรูปแบบไบนารี โดยพื้นฐานแล้วมันเก็บเครื่องหมายเศษส่วนและเลขชี้กำลังเพื่อแทน Float มันเหมือนกับสัญลักษณ์ทางวิทยาศาสตร์สำหรับไบนารี (คล้าย ๆ+1.43*10^2 ) ด้วยเหตุนี้จึงเป็นไปไม่ได้ที่จะเก็บเศษส่วนและทศนิยมในโฟลตอย่างแน่นอน

นั่นเป็นสาเหตุที่มีรูปแบบทศนิยม หากคุณทำสิ่งนี้:

irb:001:0> "%.47f" % (1.0/10)
=> "0.10000000000000000555111512312578270211815834045" # not "0.1"!

ในขณะที่ถ้าคุณทำ

irb:002:0> (1.0/10).to_s
=> "0.1" # the interprer rounds the number for you

ดังนั้นหากคุณกำลังเผชิญกับเศษส่วนเล็ก ๆ เช่นความสนใจในการรวมหรือแม้แต่ตำแหน่งทางภูมิศาสตร์ฉันขอแนะนำรูปแบบทศนิยมเนื่องจากเป็นรูปแบบทศนิยม 1.0/10เท่ากับ 0.1

อย่างไรก็ตามควรสังเกตว่าแม้จะมีความแม่นยำน้อยกว่า แต่กระบวนการลอยได้เร็วขึ้น นี่คือมาตรฐาน:

require "benchmark" 
require "bigdecimal" 

d = BigDecimal.new(3) 
f = Float(3)

time_decimal = Benchmark.measure{ (1..10000000).each { |i| d * d } } 
time_float = Benchmark.measure{ (1..10000000).each { |i| f * f } }

puts time_decimal 
#=> 6.770960 seconds 
puts time_float 
#=> 0.988070 seconds

ตอบ

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

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


ดังนั้นถ้าฉันเข้าใจถูกต้องลอยอยู่ในฐาน -2 ในขณะที่ทศนิยมอยู่ในฐาน 10? อะไรจะใช้ดีสำหรับลอย ตัวอย่างของคุณทำอะไรและสาธิตให้เห็น?
Jonathan Allard

1
คุณไม่ได้หมายถึง+1.43*2^10มากกว่า+1.43*10^2?
Cameron Martin

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

1
@adg ถูกต้อง: bigdecimal ก็เป็นทางเลือกที่ดีสำหรับสกุลเงิน
Eric Duminil

1
@adg คุณพูดถูก ฉันได้ทำงานกับแอปพลิเคชั่นการบัญชีและการเงินในช่วงไม่กี่ปีที่ผ่านมาและเราเก็บฟิลด์สกุลเงินทั้งหมดของเราในคอลัมน์จำนวนเต็ม มันปลอดภัยกว่ามากสำหรับกรณีนี้
Guilherme Lages Santos

19

ใน Rails 3.2.18,: decimal จะเปลี่ยนเป็น: จำนวนเต็มเมื่อใช้ SQLServer แต่ทำงานได้ดีใน SQLite เปลี่ยนเป็นลอยแก้ไขปัญหานี้ให้เรา

บทเรียนที่ได้รับคือ "ใช้ฐานข้อมูลการพัฒนาและการปรับใช้ที่เป็นเนื้อเดียวกันเสมอ!"


3
จุดที่ดี 3 ปีต่อมาในการทำ Rails ฉันเห็นด้วยอย่างสุดใจ
Jonathan Allard

3
"ใช้ฐานข้อมูลการพัฒนาและการปรับใช้ที่เป็นเนื้อเดียวกันเสมอ!"
zx1986

15

ใน Rails 4.1.0 ฉันประสบปัญหาเกี่ยวกับการบันทึกละติจูดและลองจิจูดไปยังฐานข้อมูล MySql ไม่สามารถบันทึกเลขเศษส่วนขนาดใหญ่ด้วยชนิดข้อมูลลอยได้ และฉันเปลี่ยนชนิดข้อมูลเป็นทศนิยมและทำงานให้ฉัน

  def เปลี่ยน
    change_column: เมือง,: ละติจูด,: ทศนิยม,: precision => 15,: scale => 13
    change_column: เมือง,: ลองจิจูด,: ทศนิยม,: precision => 15,: scale => 13
  ปลาย

ฉันบันทึกละติจูดและลองจิจูดเป็นลอยใน Postgres และใช้งานได้ดี
Scott W

3
@Robikul: ใช่มันเป็นสิ่งที่ดี แต่ overkill decimal(13,9) เพียงพอสำหรับละติจูดและลองจิจูด @ScottW: ฉันจำไม่ได้ แต่ถ้า Postgres ใช้ IEEE ลอยมันก็แค่ "ใช้งานได้ดี" เพราะคุณไม่พบปัญหา ... YET มันเป็นรูปแบบที่ไม่เพียงพอสำหรับละติจูดและลองจิจูด ในที่สุด Yo จะมีข้อผิดพลาดในตัวเลขที่สำคัญน้อยที่สุด
Lonny Eachus

@ LonnyEachus ทำให้ IEEE ลอยไม่เพียงพอสำหรับ lat / longs อย่างไร
Alexander Suraphel

3
@AlexanderSuraphel หากคุณใช้ละติจูดและลองติจูดทศนิยมการลอยของ IEEE นั้นอาจเกิดข้อผิดพลาดได้ในตัวเลขที่สำคัญน้อยที่สุด ดังนั้นละติจูดและลองจิจูดของคุณอาจมีความแม่นยำ 1 เมตร แต่คุณอาจมีข้อผิดพลาด 100 เมตรหรือมากกว่า โดยเฉพาะอย่างยิ่งหากคุณใช้ในการคำนวณ
Lonny Eachus
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.