Ответ 1
TL; DR
Выберите либо опцию № 1, либо опцию № 2 - между ними нет никакой разницы. Не используйте вариант № 3, потому что с ним неудобно работать.
Вы утверждаете, что в числах с плавающей запятой присущи неточности. Я думаю, что это заслуживает того, чтобы изучить его сначала.
При выборе системы счисления для представления числа (будь то на листе бумаги, в компьютерной схеме или где-либо еще) необходимо учитывать две отдельные проблемы:
-
его основа; а также
-
его формат.
Выберите базу, любую базу...
Ограниченный конечным пространством, нельзя представить произвольный член бесконечного множества. Например: независимо от того, сколько бумаги вы покупаете или какого размера ваш почерк, всегда можно найти целое число, которое не помещается в заданное пространство (вы можете просто добавлять дополнительные цифры, пока не закончится бумага). Итак, с целыми числами мы обычно ограничиваем наше конечное пространство представлением только тех, которые попадают в определенный интервал - например, если у нас есть место для знака [-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 и 186.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
соответственно.Но насколько точны форматы хранения с плавающей запятой, используемые нашими компьютерами?
-
Число с плавающей запятой IEEE754 с одинарной точностью (двоичное число 32) имеет значение 24 бита или
log 10 (2 24)
(более 7) цифр, т.±0.000006%
Имеет допуск менее±0.000006%
. Другими словами, это точнее, чем сказать "86.20000
". -
Число с плавающей запятой IEEE754 с двойной точностью (двоичное число 64) имеет значение 53 бита или
log 10 (2 53)
(почти 16) цифр, то есть допускает чуть более±0.00000000000001%
. Другими словами, это точнее, чем сказать "86.2000000000000
".
Самая важная вещь, которую нужно осознать, это то, что эти форматы, соответственно, более чем в десять тысяч и более чем в триллион раз точнее, чем сказать "86.2", даже если точное преобразование двоичного кода обратно в десятичное происходит с ошибочной ложной точностью (которую мы должны игнорировать: подробнее об этом в ближайшее время)!
-
Также обратите внимание, что форматы как с фиксированной, так и с плавающей точкой приведут к потере точности, если значение известно более точно, чем поддерживает формат. Такие ошибки округления могут распространяться в арифметических операциях для получения явно ошибочных результатов (что, несомненно, объясняет вашу ссылку на "присущие неточности" чисел с плавающей запятой): например, 1 ⁄ 3 × 3000
в 5- 999.99000
фиксированной точке даст 999.99000
а точнее чем 1000.00000
; и 1 ⁄ 7 − 7 ⁄ 50
в 5- 0.0028600
значении с плавающей запятой даст 0.0028600
а не 0.0028571
.
Область численного анализа посвящена пониманию этих эффектов, но важно понимать, что любая используемая система (даже выполняющая вычисления в вашей голове) уязвима для таких проблем, потому что ни один метод расчета, который гарантированно завершается, не может предложить бесконечную точность Рассмотрим, например, как рассчитать площадь круга - обязательно будет потеря точности в значении, используемом для π, которое будет распространяться на результат.
Заключение
-
В реальных измерениях следует использовать двоичную плавающую точку: она быстрая, компактная, чрезвычайно точная и не хуже чем что-либо еще (включая десятичную версию, с которой вы начали). Так как типы данных MySQL с плавающей точкой - IEEE754, это именно то, что они предлагают.
-
Валютные приложения должны использовать фиксированную точку с отрицательным значением: пока она медленная и тратит впустую память, она гарантирует, что значения не округляются до неточных величин и что пенни не теряются на большие денежные суммы. Поскольку типы данных MySQL с фиксированной запятой являются BCD-кодированными строками, это именно то, что они предлагают.
Наконец, имейте в виду, что языки программирования обычно представляют дробные значения, используя двоичные типы с плавающей запятой: поэтому, если ваша база данных хранит значения в другом формате, вам нужно быть осторожным, как они вводятся в ваше приложение, иначе они могут быть преобразованы (со всеми вытекающие из этого проблемы) на интерфейсе.
Какой вариант лучше в этом случае?
Надеюсь, я убедил вас, что ваши значения можно безопасно (и нужно) хранить в типах с плавающей запятой, не беспокоясь о каких-либо "неточностях"? Помните, они более точны, чем когда-либо было ваше хрупкое десятичное представление из трех значащих цифр: вы просто должны игнорировать ложную точность (но это всегда нужно делать, даже если используется десятичный формат с фиксированной запятой).
Что касается вашего вопроса: выберите либо опцию 1, либо 2, чем опцию 3 - это облегчает сравнение (например, чтобы найти максимальную массу, можно просто использовать MAX(mass)
, тогда как для эффективного выполнения по двум столбцам потребуется некоторое вложение).
Между этими двумя значениями не имеет значения, какой из них выбрать - числа с плавающей запятой хранятся с постоянным числом значащих битов независимо от их масштаба.
Кроме того, хотя в общем случае может случиться, что некоторые значения округляются до двоичных чисел, которые ближе к их исходному десятичному представлению, с использованием опции 1, в то время как другие округляются до двоичных чисел, которые ближе к их исходному десятичному представлению с использованием варианта 2, как вскоре мы увидим, что такие ошибки представления проявляются только в пределах ложной точности, которую всегда следует игнорировать.
Тем не менее, в этом случае, поскольку случается, что есть 16 унций на 1 фунт (а 16 - степень 2), относительные различия между исходными десятичными значениями и сохраненными двоичными числами с использованием двух подходов идентичны:
-
5.3875 10
(а не5.33671875 10
как указано в вашем вопросе) будет храниться в двоичном с плавающей точкой32 как101.011000110011001100110 2
(что составляет5.38749980926513671875 10
): это составляет0.0000036%
от исходного значения (но, как обсуждалось выше, "исходное значение" было уже довольно паршивое представление физической величины, которую оно представляет).Зная, что число с плавающей запятой двоичного числа 32 хранит только 7 десятичных цифр точности, наш компилятор точно знает, что все, начиная с 8-й цифры и далее, определенно является ложной точностью и поэтому должно игнорироваться в каждом случае - таким образом, при условии, что наше входное значение не требует большего чем точность (и если это так, то двоичный код 32 был явно неправильным выбором формата), это гарантирует возврат к десятичному значению, которое выглядит так же круглым, как и то, с которого мы начали:
5.387500 10
. Тем не менее, мы должны действительно применять знания предметной области в этой точке (как и в случае любого формата хранения), чтобы отбросить любую ложную точность, которая может существовать, например, эти два конечных нуля. -
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 миллионов долларов.