Использовать число с плавающей запятой или десятичное число для учета долларов США?

76

Мы переписываем нашу устаревшую систему учета в VB.NET и SQL Server. Мы внесли новую команду .NET/SQL Programmers, чтобы сделать переписывание. Большая часть системы уже заполнена суммами доллара с использованием Floats. Унаследованный системный язык, который я запрограммировал, не имел Float, поэтому я, вероятно, использовал бы Decimal.

Какова ваша рекомендация?

Должны ли использоваться типы данных Float или Decimal для сумм в долларах?

Каковы некоторые из плюсов и минусов для?

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

Другой Con - все дисплеи, а напечатанные суммы должны иметь формат Statement, который показывает две десятичные позиции. Я заметил несколько раз, когда это не было сделано, и суммы не выглядели правильными. (то есть 10,2 или 10,2546).

A pro - это Float занимает до 8 байтов на диске, где Decimal будет занимать 9 байтов (Decimal 12,2)

  • 5
    Вернитесь и избавьтесь от ваших поплавков.
Теги:
sql-server
database-design
currency
accounting

24 ответа

104
Лучший ответ

Следует ли использовать тип данных 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.. вверх (справа).
Учитывая, что результат вычислений с плавающей запятой всегда содержит небольшие ошибки, решение - просто удача. Это становится безнадежным, если вам нужна десятичная округленная обработка с двоичными числами.

Не убежден? Вы настаиваете, что в вашей учетной записи все отлично?
Активы и пассивы равны? Хорошо, тогда возьмите каждое из заданных отформатированных чисел каждой записи, проанализируйте их и суммируйте их с независимой десятичной системой! Сравните это с отформатированной суммой.
К сожалению, что-то не так, не так ли?

Для этого расчета требовалась предельная точность и верность (мы использовали Oracle FLOAT), чтобы мы могли зафиксировать "миллиардную копейку", которая была начислена.

Не помогает против этой ошибки. Поскольку все люди автоматически предполагают, что компьютер суммирует правильно, практически никто не проверяет самостоятельно.

  • 0
    Но обязательно используйте как минимум 4 знака после запятой в десятичном поле, если вы хотите делать вычисления на нем, особенно на деление.
  • 1
    И убедитесь, что вы знаете, что (по умолчанию) $ 0,045 округляет до $ 0,04 и $ 0,055 округляет до $ 0,06
Показать ещё 2 комментария
38
  • 2
    Это заставило меня смеяться. Путь, Best Buy.
  • 14
    Я получал счет на $ 0,01 от телефонной компании каждый месяц в течение года. Таким образом, я заплатил им 0,02 доллара онлайн, затем получил счет на - 0,01 доллара за шесть месяцев, затем он прекратился.
21

Сначала вы должны прочитать это Что каждый компьютерный ученый должен знать о арифметике с плавающей точкой. Тогда вам стоит подумать об использовании некоторого типа пакета номер с фиксированной или произвольной точностью (например, java BigNum, десятичный модуль python), иначе вы будете в мире обид. Затем выясните, достаточно ли использовать собственный десятичный тип SQL.

Floats/doubles существуют (ed), чтобы выставить быстрый x87 fp, который теперь довольно устарел. Не используйте их, если вы заботитесь о точности вычислений и/или не полностью компенсируете их ограничения.

  • 1
    Хотя изучение дополнительной информации о числах с плавающей запятой полезно, использование десятичного типа в C # похоже на использование пакета чисел с фиксированной запятой / произвольной точности, как вы предлагаете, встроенного в язык. См. Msdn.microsoft.com/en-us/library/system.decimal.aspx для объяснения того, как десятичная дробь хранит точные степени 10 с десятичными знаками вместо степеней 2 для десятичного компонента (это в основном int с компонентом размещения десятичного разделителя) ).
  • 1
    «разоблачить быстрый x87 fp, который сейчас в значительной степени устарел», то есть просто неверные числа с плавающей запятой все еще являются одним из наиболее часто используемых типов данных на компьютерах, например, симуляции, игры, обработка сигналов ...
10

Также как дополнительное предупреждение, SQL Server и .Net используют другой алгоритм по умолчанию для округления. Убедитесь, что вы проверили параметр MidPointRounding в Math.Round()..Net использует алгоритм Bankers по умолчанию, а SQL Server использует симметричное алгоритмическое округление. Просмотрите статью Википедии здесь

7

Спросите своих бухгалтеров! Они будут хмуриться вам за использование float. Как и раньше, используйте float ТОЛЬКО, если вам не нужна точность. Хотя я всегда буду против этого, когда дело доходит до денег.

В бухгалтерском программном обеспечении НЕ допускается поплавок. Используйте десятичный знак с четырьмя десятичными знаками.

6

Плавающие точки имеют неожиданные иррациональные числа.

Например, вы не можете хранить 1/3 как десятичную, это будет 0.3333333333... (и т.д.)

Поплавки фактически хранятся как двоичное значение и степень 2 экспонента.

Таким образом, 1.5 сохраняется как 3 x 2 до -1 (или 3/2)

Использование этих базовых показателей 2 создает некоторые нечетные иррациональные числа, например:

Преобразовать 1.1 в float и затем снова преобразовать его, ваш результат будет примерно таким: 1.0999999999989

Это связано с тем, что двоичное представление 1.1 на самом деле составляет 154811237190861 x 2 ^ -47, более чем двойное может обрабатывать.

Подробнее об этой проблеме на в моем блоге, но в основном для хранения вам лучше с десятичными знаками.

На сервере Microsoft SQL у вас есть тип данных money - это обычно лучше всего подходит для финансового хранилища. Точность до 4 десятичных позиций.

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

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

  • 3
    «иррациональное» - это не то слово, которое вы ищете. 1/3 все еще рациональна, но она не имеет конечного двоичного представления ...
  • 0
    Да, я знаю - я просто не знаю, как еще это назвать: число, которое невозможно представить, слишком многословно.
Показать ещё 4 комментария
5

Немного фона здесь....

Никакая система цифр не может точно обрабатывать все реальные числа. Все они имеют свои ограничения, и это включает в себя как стандартную плавающую точку IEEE, так и подписанную десятичную. Плавающая точка IEEE более точная для каждого используемого бита, но это не имеет значения здесь.

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

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

5

Я бы рекомендовал использовать 64-битные целые числа, которые хранят все в центах.

  • 0
    Вот так и я это делаю.
  • 0
    С очевидным предостережением, что частичные центовые значения (т. Е. $ 0,015) не могут быть представлены вообще. Разумное ограничение для большинства приложений.
Показать ещё 3 комментария
5

Используйте тип SQL Server десятичный.

Не используйте деньги или плавайте.

деньги используют 4 десятичных разряда, быстрее, чем использование десятичной НО страдает от некоторых очевидных и некоторых не столь очевидных проблем с округлением (см. эту проблему подключения)

  • 0
    Смотрите ответ Дэвида Торнли. Может случиться так, что тип денег наиболее точно воспроизводит правила бухгалтерского учета, однако (не) они приблизительны.
4

Еще одна вещь, о которой вам следует знать в системах учета, - это то, что никто не должен иметь прямой доступ к таблицам. Это означает, что доступ к системе учета должен осуществляться через хранимые процедуры. Это предотвращает мошенничество не только инъекций SQl. Внутренний пользователь, который хочет совершить мошенничество, не должен иметь возможность напрямую изменять данные в таблицах базы данных. Это критический внутренний контроль в вашей системе. Вы действительно хотите, чтобы какой-то недовольный сотрудник перешел на бэкэнд вашей базы данных и начал ли его проверять? Или скрыть, что они одобрили расходы неавторизованному поставщику, когда у них нет полномочий по утверждению? Только два человека в вашей организации должны иметь возможность прямого доступа к данным в вашей финансовой базе данных, вашей dba и его резервной копии. Если у вас много dbas, только два из них должны иметь этот доступ.

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

4

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

Для этого расчета требовалась высокая точность и точность (мы использовали Oracle FLOAT), чтобы мы могли записать "миллиардную долю пенни".

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

Это удовлетворило бухгалтеров, аудиторов и тестировщиков.

Итак, обратитесь к своим клиентам. Они расскажут вам о правилах и практике их банковского дела/учета.

  • 0
    Миллиардные доли пенни - это 0,01 ^ e-9 - здесь нет абсолютно никакой причины использовать FLOAT от Oracle для «предельной точности и точности», поскольку это представление с плавающей запятой, которое является приблизительным числом, а не точным числом. DECIMAL TSQL (38,18) будет более точным. Без объяснения того, как вы работали с мультивалютой, я скептически отношусь к тому, что вы не ошиблись. Если бы тестеры переходили из евро в доллар Зимбабве, они могли бы столкнуться с реальной проблемой округления.
  • 0
    Просто чтобы уточнить, я использовал поплавки для процесса начисления процентов. Десятичные дроби использовались для фактических транзакций (когда начисленные проценты были выплачены). В то время система была единой валютой. Если бы у меня было время снова, я бы не использовал поплавки. :)
4

Единственная причина использовать Float для денег - это то, что вам не нужны точные ответы.

4

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

Чтобы уточнить, десятичный тип 12,2 будет хранить эти 14 цифр точно, тогда как float не будет, поскольку он использует двоичное представление внутри. Например, 0.01 не может быть точно представлено числом с плавающей запятой - самое близкое представление на самом деле равно 0,0099999998

  • 0
    Десятичные числа тоже не точны, если они не имеют бесконечной точности.
  • 0
    0.1 может храниться точно в десятичном поле. Десятичные числа не являются точными для каждого числа , но являются точными для большинства (некоторых?) Общих денежных сумм. Иногда.
3

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

int cents = num % 100;
int dollars = (num - cents) / 100;
printf("%d.%02d", dollars, cents);

Если вам нравится иметь больше точности, вы можете изменить значение 100 на большее значение, например: 10 ^ n, где n - число десятичных знаков.

  • 3
    Вы должны сделать это, если у вас нет хорошего типа с фиксированной точкой. Достоинством является то, что вы можете определить, где находится десятичное число, а недостатком - то, что вы собираетесь его испортить. Если вы можете получить тип с фиксированной запятой, вам не придется об этом беспокоиться.
2

Из 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;
}
  • 0
    Ради этого я скопировал приведенный выше код и запустил его в кодовой панели. codepad.org/03hAQZwq Это включает вывод.
2

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

  • 0
    Я предполагаю, что вы используете глагол ROUND в ваших процедурах?
  • 0
    Если вы имеете в виду на стороне SQL, то нет. Я предпочитаю, чтобы DAL возвращал целое число, как в БД. Именно на уровне бизнес-логики я выполняю преобразование. int cents = значение% 100; int доллары = (значение - центы) / 100; С .NET 3.5 у меня есть метод расширения для этого.
2

Вы всегда можете написать что-то вроде типа Money для .Net.

Взгляните на эту статью: Тип денег для CLR - автор сделал отличную работу, на мой взгляд.

1

Вероятно, вы захотите использовать некоторую форму представления фиксированной точки для значений валюты. Вы также захотите изучить округление Banker (также известное как "round half even".) Это позволяет избежать предубеждений, которые существуют в обычном методе "round half up".

1

Рассматривали ли вы использование типа денежных данных для хранения сумм в долларах?

Относительно Con, что десятичное число занимает еще один байт, я бы сказал, что это не волнует. В 1 миллионе строк вы будете использовать только 1 МБ, и в наши дни их очень дешево.

  • 1
    Не используйте тип данных money. (Это похмелье от SyBase.)
1

Что бы вы ни делали, вам нужно быть осторожным с ошибками округления. Рассчитайте с большей степенью точности, чем вы показываете.

0

Это отличная статья, описывающая когда использовать float и decimal. Float хранит приблизительное значение, а десятичное значение сохраняет точное значение.

Таким образом, точные значения, такие как деньги, должны использовать десятичные числа, а приблизительные значения, такие как научные измерения, должны использовать 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

При умножении не целого числа и делении на тот же номер, десятичные знаки теряют точность, а float - нет.

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
0

Ваши бухгалтеры захотят контролировать, как вы обходились. Использование float означает, что вы будете постоянно округлять, как правило, с помощью оператора типа FORMAT(), который не подходит вам (используйте вместо него пол/потолок).

У вас есть типы данных валюты (деньги, smallmoney), которые следует использовать вместо float или real. Хранение десятичного числа (12,2) устранит ваши округления, но также устранит их на промежуточных этапах - это действительно не то, что вы хотите вообще в финансовом приложении.

0

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

Существует только четыре десятичных дроби, представляемых точно в двоичной с плавающей запятой: 0, 0,25, 0,5 и 0,75. Все остальное является приближением, таким же образом, что 0.3333... является приближением для 1/3 в десятичной арифметике.

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

0

Всегда используйте Decimal. Поплавок даст вам неточные значения из-за проблем округления.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню