Float vs Decimal trong ActiveRecord


283

Đôi khi, các kiểu dữ liệu Activerecord làm tôi bối rối. Err, thường xuyên. Một trong những câu hỏi muôn thuở của tôi là, đối với một trường hợp cụ thể,

Tôi nên sử dụng :decimalhay :float?

Tôi thường xuyên bắt gặp liên kết này, ActiveRecord :: thập phân vs: float? , nhưng câu trả lời không đủ rõ ràng để tôi chắc chắn:

Tôi đã thấy nhiều chủ đề mà mọi người khuyên bạn nên không bao giờ sử dụng float và luôn sử dụng số thập phân. Tôi cũng đã thấy một số người đề xuất chỉ sử dụng float cho các ứng dụng khoa học.

Dưới đây là một số trường hợp ví dụ:

  • Geolocation / vĩ độ / kinh độ: -45.756688, 120.5777777, ...
  • Tỷ lệ / Tỷ lệ: 0.9, 1.25, 1.333, 1.4143, ...

Tôi đã sử dụng :decimaltrong quá khứ, nhưng tôi thấy việc xử lý BigDecimalcác đối tượng trong Ruby là khó xử không cần thiết so với một chiếc phao. Tôi cũng biết rằng tôi có thể sử dụng :integerđể đại diện cho tiền / xu chẳng hạn, nhưng nó không hoàn toàn phù hợp với các trường hợp khác, ví dụ như khi số lượng trong đó độ chính xác có thể thay đổi theo thời gian.

  • Những lợi thế / bất lợi của việc sử dụng mỗi là gì?
  • Điều gì sẽ là một số quy tắc tốt để biết loại nào để sử dụng?

Câu trả lời:


427

Tôi nhớ giáo sư CompSci của tôi nói rằng đừng bao giờ sử dụng phao cho tiền tệ.

Lý do cho điều đó là cách đặc tả của IEEE định nghĩa các float ở định dạng nhị phân. Về cơ bản, nó lưu trữ dấu, phân số và số mũ để thể hiện một Float. Nó giống như một ký hiệu khoa học cho nhị phân (đại loại như +1.43*10^2). Do đó, không thể lưu trữ phân số và số thập phân trong Float chính xác.

Đó là lý do tại sao có định dạng thập phân. Nếu bạn làm điều này:

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

trong khi đó nếu bạn chỉ làm

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

Vì vậy, nếu bạn đang xử lý các phân số nhỏ, như lãi kép, hoặc thậm chí là định vị địa lý, tôi rất khuyến nghị định dạng thập phân, vì ở định dạng thập phân 1.0/10là chính xác 0,1.

Tuy nhiên, cần lưu ý rằng mặc dù ít chính xác hơn, phao được xử lý nhanh hơn. Đây là một điểm chuẩn:

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

Câu trả lời

Sử dụng float khi bạn không quan tâm đến độ chính xác quá nhiều. Ví dụ, một số mô phỏng và tính toán khoa học chỉ cần tối đa 3 hoặc 4 chữ số có nghĩa. Điều này rất hữu ích trong việc giao dịch với độ chính xác cho tốc độ. Vì chúng không cần độ chính xác nhiều như tốc độ, nên chúng sẽ sử dụng phao.

Sử dụng số thập phân nếu bạn đang xử lý các số cần chính xác và tổng hợp thành số chính xác (như lãi kép và những thứ liên quan đến tiền). Hãy nhớ rằng: nếu bạn cần độ chính xác, thì bạn nên luôn luôn sử dụng số thập phân.


Vì vậy, nếu tôi hiểu chính xác, float là ở cơ sở-2 trong khi thập phân là ở cơ sở-10? Điều gì sẽ là một sử dụng tốt cho phao? Ví dụ của bạn làm gì, và chứng minh?
Jonathan Allard

1
Ý bạn là +1.43*2^10không phải chứ +1.43*10^2?
Cameron Martin

46
Đối với khách truy cập trong tương lai, loại dữ liệu tốt nhất cho tiền tệ là số nguyên, không phải số thập phân. Nếu độ chính xác của trường là đồng xu thì trường sẽ là số nguyên tính bằng đồng xu (không phải là số thập phân bằng đô la). Tôi đã làm việc trong bộ phận CNTT của một ngân hàng và đó là cách nó được thực hiện ở đó. Một số trường có độ chính xác cao hơn (chẳng hạn như một phần trăm xu) nhưng chúng vẫn là số nguyên.
adg

1
@adg đã đúng: bigdecimal cũng là một lựa chọn kém cho tiền tệ.
Eric Duminil

1
@adg bạn nói đúng. Tôi đã làm việc với một số ứng dụng kế toán và tài chính trong vài năm qua và chúng tôi lưu trữ tất cả các trường tiền tệ của chúng tôi trong các cột số nguyên. Nó an toàn hơn nhiều cho trường hợp này.
Guilherme Lages Santos

19

Trong Rails 3.2.18, thập phân biến thành: số nguyên khi sử dụng SQLServer, nhưng nó hoạt động tốt trong SQLite. Chuyển sang: float đã giải quyết vấn đề này cho chúng tôi.

Bài học rút ra là "luôn luôn sử dụng cơ sở dữ liệu triển khai và phát triển đồng nhất!"


3
Điểm hay, 3 năm sau làm Rails, tôi hết lòng đồng ý.
Jonathan Allard

3
"luôn luôn sử dụng cơ sở dữ liệu phát triển và triển khai đồng nhất!"
zx1986

15

Trong Rails 4.1.0, tôi đã gặp phải vấn đề với việc lưu vĩ độ và kinh độ vào cơ sở dữ liệu MySql. Nó không thể lưu số phần lớn với kiểu dữ liệu float. Và tôi thay đổi kiểu dữ liệu thành số thập phân và làm việc cho tôi.

  thay đổi
    change_column: city ,: vĩ độ ,: thập phân ,: precision => 15 ,: scale => 13
    change_column: city ,: kinh độ ,: thập phân ,: precision => 15 ,: scale => 13
  kết thúc

Tôi lưu lại: vĩ độ và: kinh độ như trôi nổi trong Postgres, và nó hoạt động tốt.
Scott W

3
@Robikul: vâng, điều đó tốt, nhưng quá mức cần thiết. decimal(13,9) là đủ cho vĩ độ và kinh độ. @ScottW: Tôi không nhớ lại, nhưng nếu Postgres sử dụng phao của IEEE, nó chỉ "hoạt động tốt" vì bạn không gặp phải vấn đề ... YET. Nó là một định dạng không đủ cho vĩ độ và kinh độ. Yo cuối cùng sẽ có lỗi trong các chữ số có nghĩa ít nhất.
Lonny Eachus

@LonnyEachus điều gì làm cho phao của IEEE không đủ cho lat / long?
Alexander Suraphel

3
@AlexanderSuraphel Nếu bạn đang sử dụng vĩ độ và kinh độ thập phân, một số float của IEEE dễ bị lỗi trong các chữ số có nghĩa ít nhất. Vì vậy, vĩ độ và kinh độ của bạn có thể có độ chính xác 1 mét chẳng hạn, nhưng bạn có thể có lỗi từ 100 mét trở lên. Điều này đặc biệt đúng nếu bạn đang sử dụng chúng trong tính toán.
Lonny Eachus
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.