Mengambang vs Desimal di ActiveRecord

283

Terkadang, tipe data Activerecord membingungkan saya. Err, sering. Salah satu pertanyaan abadi saya adalah, untuk kasus tertentu,

Haruskah saya menggunakan :decimalatau :float?

Saya sering menemukan tautan ini, ActiveRecord:: desimal vs: float? , tetapi jawabannya tidak cukup jelas bagi saya untuk memastikan:

Saya telah melihat banyak utas di mana orang merekomendasikan flat out untuk tidak pernah menggunakan float dan selalu menggunakan desimal. Saya juga melihat saran oleh beberapa orang untuk menggunakan float hanya untuk aplikasi ilmiah.

Berikut adalah beberapa contoh kasus:

  • Geolocation / lintang / bujur: -45.756688, 120.5777777, ...
  • Rasio / persentase: 0.9, 1.25, 1.333, 1.4143, ...

Saya telah menggunakan :decimaldi masa lalu, tetapi saya menemukan berurusan dengan BigDecimalbenda - benda di Ruby tidak perlu canggung dibandingkan dengan pelampung. Saya juga tahu saya bisa menggunakan :integeruntuk mewakili uang / sen, misalnya, tetapi tidak cukup cocok untuk kasus lain, misalnya ketika jumlah di mana presisi dapat berubah dari waktu ke waktu.

  • Apa kelebihan / kekurangan menggunakan masing-masing?
  • Apa aturan praktis yang baik untuk mengetahui tipe mana yang digunakan?
Jonathan Allard
sumber

Jawaban:

427

Saya ingat profesor CompSci saya mengatakan tidak pernah menggunakan pelampung untuk mata uang.

Alasan untuk itu adalah bagaimana spesifikasi IEEE mendefinisikan float dalam format biner. Pada dasarnya, ia menyimpan tanda, fraksi dan eksponen untuk mewakili Float. Ini seperti notasi ilmiah untuk biner (sesuatu seperti +1.43*10^2). Karena itu, tidak mungkin untuk menyimpan pecahan dan desimal di Float.

Itu sebabnya ada format desimal. Jika kamu melakukan ini:

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

sedangkan jika Anda hanya melakukannya

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

Jadi jika Anda berurusan dengan pecahan kecil, seperti bunga majemuk, atau mungkin bahkan geolokasi, saya akan sangat merekomendasikan format desimal, karena dalam format desimal 1.0/10tepat 0,1.

Namun, perlu dicatat bahwa meskipun kurang akurat, float diproses lebih cepat. Inilah patokan:

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

Menjawab

Gunakan float ketika Anda tidak terlalu peduli tentang presisi. Sebagai contoh, beberapa simulasi dan perhitungan ilmiah hanya membutuhkan hingga 3 atau 4 digit signifikan. Ini berguna dalam menukar akurasi untuk kecepatan. Karena mereka tidak membutuhkan ketelitian sebanyak kecepatan, mereka akan menggunakan pelampung.

Gunakan desimal jika Anda berurusan dengan angka yang perlu tepat dan jumlahkan ke nomor yang benar (seperti bunga majemuk dan hal-hal yang berhubungan dengan uang). Ingat: jika Anda membutuhkan ketelitian, maka Anda harus selalu menggunakan desimal.

Iuri G.
sumber
Jadi jika saya mengerti benar, float ada di basis-2 sedangkan desimal di basis-10? Apa gunanya mengapung? Apa yang dilakukan contoh Anda, dan perlihatkan?
Jonathan Allard
1
Apakah maksud Anda +1.43*2^10bukan +1.43*10^2?
Cameron Martin
47
Untuk pengunjung masa depan, tipe data terbaik untuk mata uang adalah bilangan bulat, bukan desimal. Jika ketepatan bidang adalah uang maka bidang akan menjadi bilangan bulat dalam uang (bukan desimal dalam dolar). Saya bekerja di departemen TI sebuah bank dan itulah yang dilakukan di sana. Beberapa bidang presisi lebih tinggi (seperti seperseratus sen) tetapi masih bilangan bulat.
adg
1
@ adg benar: bigdecimal juga merupakan pilihan yang buruk untuk mata uang.
Eric Duminil
1
@ Adg kamu benar. Saya telah bekerja dengan beberapa aplikasi akuntansi dan keuangan selama beberapa tahun terakhir dan kami menyimpan semua bidang mata uang kami dalam kolom integer. Jauh lebih aman untuk kasus ini.
Guilherme Lages Santos
19

Dalam Rails 3.2.18,: desimal berubah menjadi: integer saat menggunakan SQLServer, tetapi berfungsi baik dalam SQLite. Beralih ke: float memecahkan masalah ini untuk kami.

Pelajaran yang dipetik adalah "selalu gunakan database pengembangan dan penyebaran yang homogen!"

ryan0
sumber
3
Poin bagus, 3 tahun kemudian melakukan Rails, saya sepenuh hati setuju.
Jonathan Allard
3
"Selalu gunakan database pengembangan dan penyebaran yang homogen!"
zx1986
15

Di Rails 4.1.0, saya menghadapi masalah dengan menyimpan lintang dan bujur ke basis data MySql. Itu tidak dapat menyimpan nomor fraksi besar dengan tipe data float. Dan saya mengubah tipe data menjadi desimal dan berfungsi untuk saya.

  perubahan def
    change_column: kota,: lintang,: desimal,: presisi => 15,: skala => 13
    change_column: kota,: bujur,: desimal,: presisi => 15,: skala => 13
  akhir
Rokibul Hasan
sumber
Saya menyimpan: garis lintang dan: garis bujur saya sebagai mengambang di Postgres, dan itu berfungsi dengan baik.
Scott W
3
@Robikul: ya, itu bagus, tapi berlebihan. decimal(13,9) cukup untuk garis lintang dan bujur. @ScottW: Saya tidak ingat, tetapi jika Postgres menggunakan IEEE float, itu hanya "berfungsi dengan baik" karena Anda belum mengalami masalah ... BELUM. Ini adalah format yang tidak memadai untuk lintang dan bujur. Yo akhirnya akan memiliki kesalahan dalam digit paling tidak signifikan.
Lonny Eachus
@ LonnyEachus apa yang membuat IEEE mengapung tidak cukup untuk lat / long?
Alexander Suraphel
3
@AlexanderSuraphel Jika Anda menggunakan lintang dan bujur desimal, float IEEE rentan terhadap kesalahan dalam digit paling tidak signifikan. Jadi lintang dan bujur Anda mungkin memiliki ketelitian 1 meter misalnya, tetapi Anda mungkin memiliki kesalahan 100 meter atau lebih. Ini terutama benar jika Anda menggunakannya dalam perhitungan.
Lonny Eachus