Использовать число с плавающей запятой или десятичное число для учета долларов США?
Мы переписываем нашу устаревшую систему учета в VB.NET и SQL Server. Мы привлекли новую команду программистов.NET/ SQL для переписывания. Большая часть системы уже укомплектована суммами в долларах с использованием чисел с плавающей запятой. У старого системного языка, который я запрограммировал, не было Float, поэтому я, вероятно, использовал бы десятичное число.
Какова ваша рекомендация?
Следует ли использовать тип данных Float или Decimal для сумм в долларах?
Каковы некоторые плюсы и минусы для обоих?
Одна из оговорок, упомянутая в нашей ежедневной схватке, заключалась в том, что вы должны быть осторожны, когда вычисляете сумму, которая возвращает результат, превышающий две десятичные позиции. Похоже, вам придется округлить сумму до двух десятичных разрядов.
Еще один недостаток - все показы, и напечатанные суммы должны иметь оператор формата, который показывает две десятичные позиции. Я заметил несколько раз, где это не было сделано, и суммы не выглядели правильными. (т.е. 10,2 или 10,2546)
Профи - это то, что Float занимает всего 8 байт на диске, тогда как десятичное число занимает 9 байтов (десятичное 12,2).
24 ответа
Следует ли использовать тип данных Float или Decimal для сумм в долларах?
Ответ прост. Никогда не плавает. НИКОГДА!
В соответствии с IEEE 754 значения с плавающей запятой всегда были двоичными, только новый стандарт IEEE 754R определял десятичные форматы. Многие дробные двоичные части никогда не могут быть равны точному десятичному представлению.
Любое двоичное число может быть записано как m/2^n
(m
, n
положительные целые числа), любое десятичное число как m/(2^n*5^n)
,
Поскольку двоичным файлам не хватает премьер factor 5
все двоичные числа могут быть точно представлены десятичными числами, но не наоборот.
0.3 = 3/(2^1 * 5^1) = 0.3
0.3 = [0.25/0.5] [0.25/0.375] [0.25/3.125] [0.2825/3.125]
1/4 1/8 1/16 1/32
Таким образом, вы получите число, большее или меньшее заданного десятичного числа. Всегда.
Почему это имеет значение? Завершают.
Нормальное округление означает 0,4 вниз, 5,9 вверх. Так что имеет значение, если результат 0.049999999999
.... или же 0.0500000000
... Вы можете знать, что это означает 5 центов, но компьютер не знает, что и раундов 0.4999
... вниз (неправильно) и 0.5000
... вверх (справа).
Учитывая, что результат вычислений с плавающей запятой всегда содержит небольшие ошибки, решение - просто удача. Это становится безнадежным, если вам нужна десятичная округленная обработка с двоичными числами.
Не убежден? Вы настаиваете, что в вашей учетной записи все отлично?
Активы и пассивы равны? Хорошо, тогда возьмите каждое из заданных отформатированных чисел каждой записи, проанализируйте их и суммируйте их с независимой десятичной системой! Сравните это с отформатированной суммой.
К сожалению, что-то не так, не так ли?
Для этого расчета требовалась предельная точность и достоверность (мы использовали FLOAT от Oracle), чтобы мы могли регистрировать "миллиардные пенни", которые были получены.
Не помогает против этой ошибки. Поскольку все люди автоматически предполагают, что компьютер суммирует правильно, практически никто не проверяет самостоятельно.
Это фото отвечает:
Это другая ситуация: человек из Нортгемптона получил письмо, в котором говорилось, что его дом будет конфискован, если он не заплатит ноль долларов и ноль центов!
Сначала вы должны прочитать это, что должен знать каждый компьютерщик об арифметике с плавающей точкой. Тогда вам действительно стоит подумать об использовании некоторого типа пакета чисел с фиксированной точкой / произвольной точности (например, java BigNum, десятичный модуль python), в противном случае вы попадете в мир боли. Затем выясните, достаточно ли использовать собственный десятичный тип SQL.
Существуют плавающие / двойные (ред.), Чтобы выставить быстрый x87 fp, который сейчас в значительной степени устарел. Не используйте их, если вы заботитесь о точности вычислений и / или не полностью компенсируете их ограничения.
В качестве дополнительного предупреждения SQL Server и платформа.Net используют другой алгоритм округления по умолчанию. Убедитесь, что вы проверили параметр MidPointRounding в Math.Round(). .Net Framework по умолчанию использует алгоритм Bankers, а SQL Server использует симметричное алгоритмическое округление. Проверьте статью в Википедии здесь
Спросите своих бухгалтеров! Они будут осуждать вас за использование поплавка. Как и кто-то, кто писал ранее, используйте float ТОЛЬКО, если вам не нужна точность. Хотя я всегда буду против, когда дело касается денег.
В бухгалтерском программном обеспечении не допускается использование поплавка. Используйте десятичную с 4 десятичными точками.
Плавающие точки имеют неожиданные иррациональные числа.
Например, вы не можете хранить 1/3 как десятичное число, это было бы 0.3333333333... (и так далее)
Плавания на самом деле хранятся в виде двоичного значения и степени 2 степени.
Таким образом, 1,5 сохраняется как 3 х 2 до -1 (или 3/2)
Используя эти показатели base-2, создайте несколько нечетных иррациональных чисел, например:
Конвертируйте 1.1 в число с плавающей точкой, а затем снова конвертируйте его, ваш результат будет примерно таким: 1.0999999999989
Это связано с тем, что двоичное представление 1.1 на самом деле 154811237190861 x 2^-47, с чем может справиться больше, чем double.
Подробнее об этой проблеме в моем блоге, но в основном для хранения лучше использовать десятичные дроби.
На сервере Microsoft SQL у вас есть money
Тип данных - это обычно лучше для финансового хранения. Это с точностью до 4 десятичных знаков.
Для вычислений у вас есть большая проблема - неточность - крошечная доля, но поместите ее в степенную функцию, и она быстро становится существенной.
Однако десятичные дроби не очень хороши для любого вида математики - например, нет встроенной поддержки десятичных степеней.
Немного предыстории здесь....
Ни одна система счисления не может точно обработать все действительные числа. У всех есть свои ограничения, и это включает в себя как стандартную IEEE с плавающей запятой, так и десятичную со знаком. IEEE с плавающей точкой является более точным для каждого используемого бита, но здесь это не имеет значения.
Финансовые показатели основаны на многовековой практике бумажных и ручек с соответствующими соглашениями. Они достаточно точны, но, что более важно, они воспроизводимы. Два бухгалтера, работающие с разными номерами и тарифами, должны прийти к одному и тому же номеру. Любая комната для несоответствия есть место для мошенничества.
Следовательно, для финансовых расчетов правильный ответ - это то, что дает тот же ответ, что и CPA, который хорош в арифметике. Это десятичная арифметика, а не с плавающей точкой IEEE.
Используйте десятичный тип SQL-сервера.
Не используйте деньги или плавайте.
деньги используют 4 знака после запятой, это быстрее, чем использование десятичного знака, НО страдает от некоторых очевидных и некоторых не очень очевидных проблем с округлением ( см. эту проблему подключения)
Я бы порекомендовал использовать 64-битные целые числа, которые хранят все это в центах.
Плавающие значения не являются точными представлениями, возможны проблемы точности, например, при добавлении очень больших и очень маленьких значений. Вот почему десятичные типы рекомендуются для валюты, хотя точность может быть достаточно редкой.
Для пояснения, десятичный тип 12,2 будет точно хранить эти 14 цифр, тогда как float не будет, так как он использует двоичное представление внутри. Например, 0,01 не может быть представлено точно числом с плавающей запятой - самое близкое представление фактически 0,0099999998
Единственная причина использовать Float для денег, если вы не заботитесь о точных ответах.
За банковскую систему, которую я помогал развивать, я отвечал за часть начисления процентов. Каждый день мой код вычислял, сколько процентов было начислено (заработано) на балансе в тот день.
Для этого расчета требовалась предельная точность и достоверность (мы использовали FLOAT от Oracle), чтобы мы могли фиксировать накопленные "миллиардные доли пенни".
Когда дело дошло до "капитализации" процентов (т.е. выплаты процентов обратно на ваш счет), сумма была округлена до копейки. Тип данных для остатков на счетах был два десятичных знака. (На самом деле это было сложнее, так как это была мультивалютная система, которая могла работать во многих десятичных разрядах - но мы всегда округляли до "копейки" этой валюты). Да - там, где "доли" потерь и прибылей, но когда цифры компьютеров были актуализированы (деньги выплачены или выплачены), это всегда были РЕАЛЬНЫЕ денежные ценности.
Это устраивает бухгалтеров, аудиторов и тестировщиков.
Итак, уточняйте у ваших клиентов. Они расскажут вам свои банковские / бухгалтерские правила и практики.
Даже лучше, чем использование десятичных чисел, это использование простых старых целых чисел (или, возможно, какого-то типа bigint). Таким образом, вы всегда имеете максимально возможную точность, но точность можно указать. Например номер 100
может означать 1.00
, который отформатирован так:
int cents = num % 100;
int dollars = (num - cents) / 100;
printf("%d.%02d", dollars, cents);
Если вы хотите повысить точность, вы можете изменить значение 100 на большее, например: 10 ^ n, где n - количество десятичных знаков.
Еще одна вещь, о которой вы должны знать в системах бухгалтерского учета, это то, что никто не должен иметь прямой доступ к таблицам. Это означает, что весь доступ к системе учета должен осуществляться через хранимые процедуры. Это предотвращает мошенничество, а не только инъекции SQl. Внутренний пользователь, который хочет совершить мошенничество, не должен иметь возможности напрямую изменять данные в таблицах базы данных. Это критический внутренний контроль в вашей системе. Вы действительно хотите, чтобы какой-то недовольный сотрудник пошел в бэкэнд вашей базы данных и начал писать им чеки? Или скрыть, что они утвердили расходы неавторизованному поставщику, если у них нет полномочий на утверждение? Только два человека во всей вашей организации должны иметь прямой доступ к данным в вашей финансовой базе данных, вашей базе данных и его резервной копии. Если у вас много dbas, только два из них должны иметь этот доступ.
Я упоминаю об этом, потому что, если ваши программисты использовали float в системе бухгалтерского учета, скорее всего, они совершенно не знакомы с идеей внутреннего контроля и не учитывали их в своих усилиях по программированию.
Я использовал SQL-тип денег для хранения денежных значений. Недавно мне приходилось работать с рядом систем онлайн-платежей, и я заметил, что некоторые из них используют целые числа для хранения денежных значений. В моих текущих и новых проектах я начал использовать целые числа, и я довольно доволен этим решением.
Вы всегда можете написать что-то типа Money для.Net.
Взгляните на эту статью: Тип денег для CLR - Автор сделал отличную работу, на мой взгляд.
Из 100 дробей n/100, где n - натуральное число, такое, что 0 <= n и n < 100, только четыре могут быть представлены как числа с плавающей запятой. Посмотрите на вывод этой программы на C:
#include <stdio.h>
int main()
{
printf("Mapping 100 numbers between 0 and 1 ");
printf("to their hexadecimal exponential form (HEF).\n");
printf("Most of them do not equal their HEFs. That means ");
printf("that their representations as floats ");
printf("differ from their actual values.\n");
double f = 0.01;
int i;
for (i = 0; i < 100; i++) {
printf("%1.2f -> %a\n",f*i,f*i);
}
printf("Printing 128 'float-compatible' numbers ");
printf("together with their HEFs for comparison.\n");
f = 0x1p-7; // ==0.0071825
for (i = 0; i < 0x80; i++) {
printf("%1.7f -> %a\n",f*i,f*i);
}
return 0;
}
Что бы вы ни делали, вы должны быть осторожны с ошибками округления. Рассчитать, используя большую степень точности, чем вы отображаете в.
Возможно, вы захотите использовать некоторую форму представления с фиксированной точкой для значений валюты. Вы также захотите исследовать округление Банкира (также известное как "округление до половины"). Оно позволяет избежать смещения, существующего в обычном методе "округление до половины".
Рассматривали ли вы использовать тип данных money для хранения долларовых сумм?
Что касается Con, что десятичное число занимает еще один байт, я бы сказал, что это не волнует. В 1 миллионе строк вы будете использовать только еще 1 МБ, а хранилище очень дешево в наши дни.
Ваши бухгалтеры захотят контролировать ваш кругозор. Использование float означает, что вы будете постоянно округлять, обычно с помощью оператора типа FORMAT(), а это не то, что вам нужно (используйте вместо этого floor / floor).
У вас есть типы данных валюты (money, smallmoney), которые следует использовать вместо float или real. Хранение десятичной дроби (12,2) исключит ваши округления, но также устранит их на промежуточных этапах - что на самом деле совсем не то, что вам нужно в финансовом приложении.
Это отличная статья, описывающая, когда использовать float и decimal. Float хранит приблизительное значение, а десятичное - точное значение.
Таким образом, точные значения, такие как деньги, должны использовать десятичное число, а приближенные значения, такие как научные измерения, должны использовать число с плавающей точкой.
Вот интересный пример, который показывает, что как float, так и decimal способны терять точность. При добавлении числа, которое не является целым числом, и последующем вычитании того же числа с плавающей запятой, теряется точность, в то время как десятичная дробь -
DECLARE @Float1 float, @Float2 float, @Float3 float, @Float4 float;
SET @Float1 = 54;
SET @Float2 = 3.1;
SET @Float3 = 0 + @Float1 + @Float2;
SELECT @Float3 - @Float1 - @Float2 AS "Should be 0";
Should be 0
----------------------
1.13797860024079E-15
При умножении нецелого числа и делении на это же число десятичные дроби теряют точность, а плавающие - нет.
DECLARE @Fixed1 decimal(8,4), @Fixed2 decimal(8,4), @Fixed3 decimal(8,4);
SET @Fixed1 = 54;
SET @Fixed2 = 0.03;
SET @Fixed3 = 1 * @Fixed1 / @Fixed2;
SELECT @Fixed3 / @Fixed1 * @Fixed2 AS "Should be 1";
Should be 1
---------------------------------------
0.99999999999999900
Всегда используйте десятичную. Float выдаст вам неточные значения из-за проблем с округлением.
Числа с плавающей запятой могут представлять только числа, которые являются суммой отрицательных кратных основанию - для двоичной с плавающей запятой, конечно, это два.
Есть только четыре десятичных дроби, представляемых точно в двоичной с плавающей запятой: 0, 0,25, 0,5 и 0,75. Все остальное является приближением, так же как 0,3333... является приближением для 1/3 в десятичной арифметике.
С плавающей точкой хороший выбор для вычислений, где важен масштаб результата. Это плохой выбор, когда вы пытаетесь быть точными с некоторым количеством знаков после запятой.