Лучшая практика хранения весов в базе данных SQL?

Приложение, над которым я работаю, должно хранить вес формата X pounds, y.y ounces. База данных - это MySQL, но я предполагаю, что это не зависит от базы данных.

Я могу придумать три способа сделать это:

  • Преобразуйте вес в десятичные килограммы и сохраните в одном поле. (5 фунтов 6.2 oz = 5.33671875 lbs)
  • Преобразование веса в десятичные унции и сохранение в одном поле. (5 фунтов 6.2 oz = 86.2 oz)
  • Храните фунты как целое число и часть унций как десятичное число в двух полях.

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

Есть ли веская причина выбрать # 2 над # 3 или наоборот?

Ответ 1

TL; DR

Выберите либо опцию № 1, либо опцию № 2 - между ними нет никакой разницы. Не используйте вариант № 3, потому что с ним неудобно работать.

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

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

  1. его основа; а также

  2. его формат.

Выберите базу, любую базу...

Ограниченный конечным пространством, нельзя представить произвольный член бесконечного множества. Например: независимо от того, сколько бумаги вы покупаете или какого размера ваш почерк, всегда можно найти целое число, которое не помещается в заданное пространство (вы можете просто добавлять дополнительные цифры, пока не закончится бумага). Итак, с целыми числами мы обычно ограничиваем наше конечное пространство представлением только тех, которые попадают в определенный интервал - например, если у нас есть место для знака [-999,+999]/минус и трех цифр, мы можем ограничиться этим интервалом [-999,+999].

Каждый непустой интервал содержит бесконечный набор действительных чисел. Другими словами, независимо от того, какой интервал переходит к действительным числам - будь то [-999,+999], [0,1], [0.000001,0.000002] или что-то еще, - внутри по-прежнему существует бесконечный набор действительных чисел. этот интервал (нужно только добавлять (отличные от нуля) дробные цифры)! Поэтому произвольные действительные числа всегда должны быть "округлены" до того, что может быть представлено в конечном пространстве.

Множество действительных чисел, которые могут быть представлены в конечном пространстве, зависит от используемой системы счисления. В нашей (знакомой) позиционной системе base-10 конечное пространство будет достаточным для половины ( 0.5 10), но не для одной трети ( 0.33333… 10); напротив, в (менее знакомой) системе позиционных основ -9 все наоборот (те же самые числа равны 0.44444… 9 и 0.3 9). Следствием всего этого является то, что некоторые числа, которые могут быть представлены с использованием лишь небольшого количества пространства в позиционной базе-10 (и, следовательно, представляются очень "круглыми" для нас, людей), например, одна десятая, на самом деле требуют бесконечного двоичного числа схемы должны быть сохранены точно (и, следовательно, не выглядят слишком "круглыми" для наших цифровых друзей)! Примечательно, что, поскольку 2 - это коэффициент 10, то же самое не верно в обратном порядке: любое число, которое может быть представлено конечным двоичным числом, также может быть представлено конечным десятичным числом.

Мы не можем сделать лучше для непрерывных количеств. В конечном счете, такие величины должны использовать конечное представление в некоторой системе счисления: произвольно, будет ли эта система легкой в компьютерных схемах, на человеческих пальцах, на чем-то другом или вообще ни на чем - какую бы систему ни использовали, значение должно быть округлено и поэтому это всегда приводит к "ошибке представления".

Другими словами, даже если у кого-то есть совершенно точный измерительный прибор (что физически невозможно), то любое измерение, о котором оно сообщает, уже будет округлено до числа, которое соответствует размеру его дисплея (на любой его основе - обычно десятичной, по понятным причинам). Таким образом, "86,2 унции" на самом деле никогда не является "86,2 унцией", а скорее представляет собой "что-то между 86.1500000... унцией и 86.2499999... унцией". (На самом деле, поскольку на самом деле инструмент несовершенен, все, что мы можем когда-либо на самом деле сказать, это то, что у нас есть некоторая степень уверенности в том, что фактическое значение попадает в этот интервал, но это определенно отклоняется от этой точки)

Но мы можем сделать лучше для дискретных величин. Такие значения не являются "произвольными действительными числами", и, следовательно, ни одно из вышеперечисленного не относится к ним: они могут быть представлены точно в той системе счисления, в которой они были определены - и, действительно, должны быть (как преобразование в другую систему счисления и усечение до конечная длина приведет к округлению до неточного числа). Компьютеры могут (неэффективно) обрабатывать такие ситуации, представляя число в виде строки: например, рассмотрим кодировку ASCII или BCD.

Применить формат...

Поскольку это свойство основы системы счисления (несколько произвольно), то, является ли значение "круглым", не влияет на его точность. Это действительно важное наблюдение, которое противоречит интуиции многих людей (и по этой причине я потратил так много времени на объяснение численной основы выше).

Точность определяется тем, сколько значимых цифр имеет представление. Нам нужен формат хранения, способный записывать наши значения, по крайней мере, на столько значащих цифр, сколько мы считаем их правильными. Взяв в качестве примера значения, которые мы считаем правильными, когда они определены как 86.2 и 0.0000862, два наиболее распространенных варианта:

  • Фиксированная точка, где количество значащих цифр зависит от величины: например, в фиксированном представлении с 5 десятичными точками наши значения будут сохранены как 86.20000 и 0.00009 (и, следовательно, имеют 7 и 1 86.20000 0.00009 точности соответственно). В этом примере точность была потеряна в последнем значении (и, действительно, нам не понадобилось бы намного больше, чтобы мы были совершенно неспособны представить что-либо значимое); и прежнее значение хранило ложную точность, которая является пустой тратой нашего конечного пространства (и действительно, для того, чтобы значение стало настолько большим, что оно переполняет емкость хранилища, не потребовалось бы намного больше).

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

    Обычно реализуют хранение с фиксированной запятой, обрабатывая одно значение как частное по общему знаменателю и сохраняя числитель как целое число. В нашем примере общим знаменателем может быть 10 5 поэтому вместо 86.20000 и 0.00009 можно хранить целые числа 8620000 и 9 и помнить, что они должны быть разделены на 100000.

  • С плавающей точкой, где число значащих цифр является постоянным независимо от величины: например, в десятичном представлении с 5 значащими цифрами наши значения будут храниться как 86.200 и 0.000086200 (и, по определению, иметь 5 значащих цифр точности оба раза). В этом примере оба значения были сохранены без потери точности; и они оба также имеют одинаковую степень ложной точности, которая менее затратна (и поэтому мы можем использовать наше конечное пространство для представления гораздо большего диапазона значений - как больших, так и малых).

    Распространенным примером того, когда этот формат может быть подходящим, является запись любых реальных измерений: точность измерительных приборов (которые страдают как от систематических, так и от случайных ошибок) довольно постоянна независимо от масштаба, поэтому, учитывая достаточно значимые цифры (обычно около 3 или 4 цифры), абсолютно никакая точность не теряется, даже если изменение базы привело к округлению до другого числа.

    Обычно реализуют хранилище с плавающей запятой, обрабатывая одно значение как целочисленные значения с целыми показателями. В нашем примере значение33 может быть 86200 для обоих значений, при этом показатели (base-10) будут -4 и -9 соответственно.

    Но насколько точны форматы хранения с плавающей запятой, используемые нашими компьютерами?

    Самая важная вещь, которую нужно осознать, это то, что эти форматы, соответственно, более чем в десять тысяч и более чем в триллион раз точнее, чем сказать "86.2", даже если точное преобразование двоичного кода обратно в десятичное происходит с ошибочной ложной точностью (которую мы должны игнорировать: подробнее об этом в ближайшее время)!

Также обратите внимание, что форматы как с фиксированной, так и с плавающей точкой приведут к потере точности, если значение известно более точно, чем поддерживает формат. Такие ошибки округления могут распространяться в арифметических операциях для получения явно ошибочных результатов (что, несомненно, объясняет вашу ссылку на "присущие неточности" чисел с плавающей запятой): например, 13 × 3000 в 5- 999.99000 фиксированной точке даст 999.99000 а точнее чем 1000.00000; и 17750 в 5- 0.0028600 значении с плавающей запятой даст 0.0028600 а не 0.0028571.

Область численного анализа посвящена пониманию этих эффектов, но важно понимать, что любая используемая система (даже выполняющая вычисления в вашей голове) уязвима для таких проблем, потому что ни один метод расчета, который гарантированно завершается, не может предложить бесконечную точность Рассмотрим, например, как рассчитать площадь круга - обязательно будет потеря точности в значении, используемом для π, которое будет распространяться на результат.

Заключение

  1. В реальных измерениях следует использовать двоичную плавающую точку: она быстрая, компактная, чрезвычайно точная и не хуже чем что-либо еще (включая десятичную версию, с которой вы начали). Так как типы данных MySQL с плавающей точкой - IEEE754, это именно то, что они предлагают.

  2. Валютные приложения должны использовать фиксированную точку с отрицательным значением: пока она медленная и тратит впустую память, она гарантирует, что значения не округляются до неточных величин и что пенни не теряются на большие денежные суммы. Поскольку типы данных MySQL с фиксированной запятой являются BCD-кодированными строками, это именно то, что они предлагают.

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

Какой вариант лучше в этом случае?

Надеюсь, я убедил вас, что ваши значения можно безопасно (и нужно) хранить в типах с плавающей запятой, не беспокоясь о каких-либо "неточностях"? Помните, они более точны, чем когда-либо было ваше хрупкое десятичное представление из трех значащих цифр: вы просто должны игнорировать ложную точность (но это всегда нужно делать, даже если используется десятичный формат с фиксированной запятой).

Что касается вашего вопроса: выберите либо опцию 1, либо 2, чем опцию 3 - это облегчает сравнение (например, чтобы найти максимальную массу, можно просто использовать MAX(mass), тогда как для эффективного выполнения по двум столбцам потребуется некоторое вложение).

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

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

Тем не менее, в этом случае, поскольку случается, что есть 16 унций на 1 фунт (а 16 - степень 2), относительные различия между исходными десятичными значениями и сохраненными двоичными числами с использованием двух подходов идентичны:

  1. 5.3875 10 (а не 5.33671875 10 как указано в вашем вопросе) будет храниться в двоичном с плавающей точкой32 как 101.011000110011001100110 2 (что составляет 5.38749980926513671875 10): это составляет 0.0000036% от исходного значения (но, как обсуждалось выше, "исходное значение" было уже довольно паршивое представление физической величины, которую оно представляет).

    Зная, что число с плавающей запятой двоичного числа 32 хранит только 7 десятичных цифр точности, наш компилятор точно знает, что все, начиная с 8-й цифры и далее, определенно является ложной точностью и поэтому должно игнорироваться в каждом случае - таким образом, при условии, что наше входное значение не требует большего чем точность (и если это так, то двоичный код 32 был явно неправильным выбором формата), это гарантирует возврат к десятичному значению, которое выглядит так же круглым, как и то, с которого мы начали: 5.387500 10. Тем не менее, мы должны действительно применять знания предметной области в этой точке (как и в случае любого формата хранения), чтобы отбросить любую ложную точность, которая может существовать, например, эти два конечных нуля.

  2. 86.2 10 будет храниться в двоичном с плавающей точкой32 как 1010110.00110011001100110 2 (что составляет 86.1999969482421875 10): это также 0.0000036% от исходного значения. Как и прежде, мы игнорируем ложную точность, чтобы вернуться к нашему исходному вводу.

Обратите внимание, что двоичные представления чисел идентичны, за исключением размещения радикальной точки (которая разделена четырьмя битами):

101.0110 00110011001100110
101 0110.00110011001100110

Это потому, что 5,3875 × 2 4= 86,2.

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

Ответ 2

Id будет соблазн хранить его в метрической единице, поскольку они имеют тенденцию быть простыми десятичными знаками, а не сложными значениями, такими как фунты и унции. Таким образом, вы можете просто сохранить одно значение (т.е. 103,25 кг), а не эквивалент в фунтах унций, и его легче выполнять преобразования.

Это то, с чем я имел дело в прошлом. Я много работаю над сайтами про-борца и смешанных боевых искусств (MMA), где нужно записывать высоты и веса бойцов. Они, как правило, отображаются в виде футов и дюймов, фунтов и унций, но я все еще сохраняю значения в их сантиметрах и эквивалентах килограммов, а затем делаю конверсию при отображении на сайте.

Ответ 3

Во-первых, я не знал о том, что числа с плавающей запятой были неточными - к счастью, поиск помогает мне понять: Примеры неточности с плавающей запятой

Я бы полностью согласился с @eggyal - сохранял данные в одном формате в одном столбце. Это позволяет вам разоблачить его в приложении и позволить приложению разобраться с его представлением - будь то в фунтах/унциях, округленном фунте, что угодно.

В базе данных должны храниться необработанные данные, в то время как уровень представления определяет макет.