Число с плавающей запятой и десятичное в ActiveRecord

283

Иногда типы данных Activerecord сбивают меня с толку. Часто Один из моих вечных вопросов, для данного случая,

Я должен использовать :decimalили :float?

Я часто сталкивался с этой ссылкой ActiveRecord:: decimal vs: float? , но ответы не совсем ясны, чтобы я был уверен:

Я видел много потоков, в которых люди рекомендуют использовать упущение, чтобы никогда не использовать float и всегда использовать десятичную. Я также видел предложения некоторых людей использовать float только для научных приложений.

Вот несколько примеров:

  • Геолокации / широта / долгота: -45.756688, 120.5777777, ...
  • Соотношение / процент: 0.9, 1.25, 1.333, 1.4143, ...

Я использовал :decimalв прошлом, но я обнаружил, что работа с BigDecimalобъектами в Ruby была излишне неловкой по сравнению с плавающей точкой. Я также знаю, что могу использовать :integer, например, для представления денег / центов, но это не совсем подходит для других случаев, например, когда количества, в которых точность может меняться со временем.

  • Каковы преимущества / недостатки использования каждого?
  • Какие бы хорошие эмпирические правила знать, какой тип использовать?
Джонатан Аллард
источник

Ответы:

427

Я помню, как мой профессор CompSci говорил, что никогда не используйте поплавки для валюты.

Это объясняется тем, что спецификация IEEE определяет числа с плавающей запятой в двоичном формате. По сути, он хранит знак, дробь и показатель степени для представления числа с плавающей точкой. Это как научная запись для двоичного (что-то вроде +1.43*10^2). Из-за этого невозможно точно хранить дроби и десятичные дроби в Float.

Вот почему существует десятичный формат. Если вы делаете это:

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

Ответ

Используйте float, если вам не нужна точность. Например, для некоторых научных симуляций и расчетов требуется всего 3 или 4 значащих цифры. Это полезно в обмене на точность для скорости. Поскольку они не нуждаются в точности как скорость, они будут использовать float.

Используйте десятичную дробь, если вы имеете дело с числами, которые должны быть точными, и суммировать до правильного числа (например, накапливать проценты и связанные с деньгами вещи). Помните: если вам нужна точность, то вы всегда должны использовать десятичную.

Юрий Григорьевич
источник
Так что, если я правильно понимаю, float находится в base-2, а decimal в base-10? Что было бы полезно для поплавка? Что делает ваш пример и демонстрирует?
Джонатан Аллард
1
Ты имеешь в виду, +1.43*2^10а не +1.43*10^2?
Кэмерон Мартин
47
Для будущих посетителей лучшим типом данных для валюты является целое число, а не десятичное. Если точность поля равна пенни, тогда поле будет целым числом в копейках (а не десятичной дробью в долларах). Я работал в отделе информационных технологий банка, и именно так это было сделано там. Некоторые поля были с более высокой точностью (например, сотые доли копейки), но они все еще были целыми числами.
ADG
1
@adg прав: bigdecimal также плохой выбор для валюты.
Эрик
1
@adg ты прав. За последние несколько лет я работал с некоторыми бухгалтерскими и финансовыми приложениями, и мы храним все наши валютные поля в целочисленных столбцах. Это гораздо безопаснее для таких случаев.
Гильерме Лагес Сантос
19

В Rails 3.2.18: десятичное значение превращается в: целое число при использовании SQLServer, но оно прекрасно работает в SQLite. Переход на: float решил эту проблему для нас.

Извлеченный урок: «Всегда используйте однородные базы данных разработки и развертывания!»

ryan0
источник
3
Хороший вопрос, через 3 года занятий Rails я полностью согласен.
Джонатан Аллард
3
"всегда используйте однородные базы данных разработки и развертывания!"
zx1986
15

В Rails 4.1.0 я столкнулся с проблемой сохранения широты и долготы в базе данных MySql. Он не может сохранить большое число дробей с типом данных с плавающей точкой. И я меняю тип данных на десятичный и работаю для меня.

  изменение def
    change_column: города,: широта,: десятичная дробь,: точность => 15,: масштаб => 13
    change_column: города,: долгота,: десятичная дробь,: точность => 15,: масштаб => 13
  конец
Рокибул Хасан
источник
Я сохраняю свои: широта и: долгота как плавающие в Postgres, и это прекрасно работает.
Скотт W
3
@Robikul: да, это хорошо, но излишне. decimal(13,9) достаточно для широты и долготы. @ ScottW: Я не помню, но если Postgres использует плавающие объекты IEEE, это только «работает нормально», потому что у вас не возникло проблем ... Это недостаточный формат для широты и долготы. В конечном итоге у Yo будут ошибки в младших разрядах.
Lonny Eachus
@LonnyEachus, что делает IEEE поплавками недостаточными для длинных / длинных?
Александр Сурафел
3
@AlexanderSuraphel Если вы используете десятичную широту и долготу, число с плавающей запятой IEEE подвержено ошибкам в младших разрядах. Таким образом, ваша широта и долгота могут иметь точность, например, 1 метр, но вы можете иметь ошибки 100 метров или более. Это особенно верно, если вы используете их в расчетах.
Lonny Eachus