Float vs Decimal в ActiveRecord

Иногда меня смущают типы данных Activerecord. Эээ, часто. Один из моих вечных вопросов в данном случае:

Что мне использовать: :decimal или :float?

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

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

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

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

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

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

person Jonathan Allard    schedule 15.12.2011    source источник


Ответы (3)


Я помню, как мой профессор 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.

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

person Iuri G.    schedule 15.12.2011
comment
Итак, если я правильно понимаю, float находится в базе-2, а десятичный - в базе-10? Что было бы хорошо для поплавка? Что ваш пример делает и демонстрирует? - person Jonathan Allard; 16.12.2011
comment
нет. оба они базовые 2. Разница в структуре. В моем примере показано, что 1,0 / 10 равно 0,1 при представлении десятичной структурой данных и 0,100000000000000005 при представлении структурой данных с плавающей запятой. Поэтому, если вы работаете с числами, требующими высокой точности, использование Float будет ошибкой. - person Iuri G.; 04.01.2012
comment
Когда следует использовать Float, а когда Decimal? - person Jonathan Allard; 22.10.2012
comment
@jonallard используйте float, когда вам нужна скорость и не слишком заботитесь о точности. например, некоторые научные симуляции и расчеты учитывают только 3 или 4 значащие цифры. Поскольку им не нужна точность, а скорость, вы должны использовать float. но если вы имеете дело с числами, которые должны быть точными и суммировать до правильного числа (например, вычисление процентов и вещей, связанных с деньгами), вам всегда следует использовать десятичные дроби. - person Iuri G.; 23.10.2012
comment
@luri G. Ответ хороший. Но пример не поясняет, как работает BigDecimal. Если я сделаю это,% .47f% (BigDecimal.new (1) / 10), я получу такой же результат, как если бы я все равно использовал float. - person Phương Nguyễn; 31.05.2014
comment
Разве вы не имеете в виду +1.43*2^10, а не +1.43*10^2? - person Cameron Martin; 11.06.2014
comment
@CameronMartin Нет. Я привел пример десятичной записи в научных представлениях. По сути, это пример научного обозначения, если вы не знаете, что это такое. - person Iuri G.; 11.06.2014
comment
Для будущих посетителей лучший тип данных для валюты - целое число, а не десятичное. Если точность поля - копейки, тогда поле будет целым числом в пенни (а не десятичной дробью в долларах). Я работал в ИТ-отделе банка, и вот так там и делали. Некоторые поля имели более высокую точность (например, сотые доли пенни), но они все равно были целыми числами. - person adg; 25.08.2016
comment
если они оба имеют основание 2, всегда будет ошибка округления. вроде как представление 1/3 в базе 10, не так ли? - person BKSpurgeon; 31.08.2016
comment
В основном правильно. Дело не в том, что он может быть неправильным, потому что хранит вещи как дроби. Если он хранится в дробях, он может быть настолько точным, насколько это необходимо, используя дробь типа x / 10 ^ y. Скорее, рассматриваемая дробь представляет собой двоичное число, ограниченное размером слова, и это может иметь нелогичные эффекты при округлении до доступного размера слова. Однако десятичные дроби хранят числа в более текстовой форме с указанным количеством десятичных знаков. - person Shayne; 12.09.2016
comment
@adg прав: bigdecimal - тоже плохой выбор для валюты. - person Eric Duminil; 30.01.2017
comment
@adg ты прав. Я работал с некоторыми бухгалтерскими и финансовыми приложениями в течение последних нескольких лет, и мы храним все наши валютные поля в целочисленных столбцах. В этом случае намного безопаснее. - person Guilherme Lages Santos; 06.11.2018
comment
Для будущих посетителей подходит @adg - если вы не реализуете криптовалюты, суммы, используемые для выражения сумм в пенни для ETH, например (1 wei = 10 ^ -18 ETH), просто невозможно сделать с целым числом, или бигинт. - person bbozo; 10.02.2020
comment
Помните, что ETH использует 32-байтовое int для хранения своей суммы. - person bbozo; 10.02.2020

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

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

person ryan0    schedule 22.10.2014
comment
Хороший момент, я полностью согласен с тем, что спустя 3 года после создания Rails. - person Jonathan Allard; 22.10.2014
comment
всегда используйте однородные базы данных для разработки и развертывания! - person zx1986; 07.07.2016
comment
Эта подсказка мне помогла. Спасибо за это! - person Babajide M. Moibi; 15.07.2020

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

  def change
    change_column :cities, :latitude, :decimal, :precision => 15, :scale => 13
    change_column :cities, :longitude, :decimal, :precision => 15, :scale => 13
  end
person Rokibul Hasan    schedule 03.03.2015
comment
Я сохраняю свои: latitude и: longitude как float в Postgres, и это прекрасно работает. - person Scott W; 23.11.2015
comment
@Robikul: да, это хорошо, но перебор. decimal(13,9) достаточно для широты и долготы. @ScottW: Я не помню, но если Postgres использует числа с плавающей запятой IEEE, он работает нормально только потому, что у вас не было проблем ... ЕЩЕ. Это недостаточный формат для широты и долготы. Со временем у вас будут ошибки в младших разрядах. - person Lonny Eachus; 24.06.2016
comment
@LonnyEachus, что делает IEEE float недостаточным для широты и долготы? - person Alexander Suraphel; 21.11.2016
comment
@AlexanderSuraphel Если вы используете десятичную широту и долготу, число с плавающей запятой IEEE подвержено ошибкам в младших разрядах. Таким образом, ваши широта и долгота могут иметь точность, например, в 1 метр, но у вас могут быть ошибки в 100 метров и более. Это особенно верно, если вы используете их в расчетах. - person Lonny Eachus; 04.12.2016