Использовать плавающую или десятичную дробь для расчета суммы в долларах приложения?

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

Что вы порекомендуете?

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

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

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

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

Плюс в том, что Float занимает на диске всего 8 байтов, а Decimal - 9 байтов (Decimal 12,2).

Вернитесь и избавьтесь от поплавков.

Loren Pechtel 24.10.2010 00:26
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
75
1
44 192
24
Перейти к ответу Данный вопрос помечен как решенный

Ответы 24

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

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

Десятичные дроби также не являются точными, если они не имеют бесконечной точности.

1800 INFORMATION 15.09.2008 09:00

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

rocketmonkeys 07.12.2010 00:59

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

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

Не используйте тип данных money. (Это пережиток SyBase.)

Mitch Wheat 03.11.2008 04:00

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

Единственная причина использовать Float для денег - это если вы не заботитесь о точных ответах.

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

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

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

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

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

Chris Moschini 03.06.2012 06:25

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

markmnl 09.09.2015 10:26

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

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

Фактически числа с плавающей запятой хранятся как двоичное значение и степень степени 2.

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

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

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

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

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

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

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

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

"иррациональный" - не то слово, которое вы ищете. 1/3 по-прежнему рациональна, но у нее нет конечного двоичного представления ...

Brian Postow 09.02.2009 20:03

Да, я знаю - я просто не знаю, как еще это назвать: число, которое невозможно представить, слишком многословно.

Keith 10.02.2009 18:25

Это приближения, но тогда числа, которые можно было бы представить, можно было бы также приблизить. Фактическое иррациональное число - это такое, которое не может быть представлено какой-либо целой дробью, независимо от основания. Это числа, которые могут быть представлены в базе 10, но не могут быть представлены в базе 2.

Keith 18.05.2009 15:54

Число с бесконечным десятичным представлением - это является слишком многословно!

Kenny Evitt 03.05.2011 18:07

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

Kenny Evitt 03.05.2011 18:08

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

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

Когда дело доходило до «капитализации» процентов (т. Е. Возврата процентов на ваш счет), сумма округлялась до пенни. Тип данных для остатков на счетах - два десятичных знака. (На самом деле это было сложнее, поскольку это была мультивалютная система, которая могла работать со многими десятичными знаками - но мы всегда округляли до «пенни» этой валюты). Да - там были «доли» потерь и прибыли, но когда цифры компьютеров были актуализированы (выплачены или выплачены деньги), это всегда были НАСТОЯЩИЕ денежные ценности.

Это удовлетворило бухгалтеров, аудиторов и испытателей.

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

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

John Zabroski 16.08.2014 00:11

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

Guy 18.08.2014 14:16

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

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

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

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

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

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

Вероятно, вы захотите использовать некоторую форму представления с фиксированной точкой для денежных значений. Вы также захотите исследовать округление Банкира (также известное как «округление до половины»). Оно позволяет избежать предвзятости, присущей обычному методу «округления до половины».

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

Ответ принят как подходящий

Should Float or Decimal data type be used for dollar amounts?

Ответ прост. Никогда не плавает. НИКОГДА!

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

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

For that calculation, extreme accuracy and fidelity was required (we used Oracle's FLOAT) so we could record the "billionth's of a penny" being accured.

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

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

HLGEM 09.02.2009 19:49

И убедитесь, что вы знаете, что (по умолчанию) 0,045 доллара округляется до 0,04 доллара, а 0,055 доллара округляется до 0,06 доллара.

Keith 10.02.2009 18:28

Для тех, кто не понимает, что имеет в виду Кейт, в десятичных типах используется другой вид округления. Кажется, это обычно называют «банковским округлением», но в Википедии есть несколько альтернативных названий: округление от половины до четного, несмещенное округление, конвергентное округление, статистическое округление, округление по голландскому языку, округление по Гауссу или округление банкиров (en.wikipedia.org/wiki/…).

patridge 19.02.2010 20:39

Также следует иметь в виду, что Decimal.Round и String.Format дают разные результаты: Decimal.Round (0,045M, 2) = 0,04 и String.Format ("{0: 0.00}", 0,045M) = 0,05.

Jason Massey 28.08.2012 01:36

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

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

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

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

Michael Kohne 09.02.2009 19:49

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

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

Используйте тип десятичный SQL-сервера.

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

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

См. Ответ @David Thornley. май - тип Деньги наиболее точно воспроизводит правила бухгалтерского учета, однако (не) приблизительно они являются.

Roy Tinker 07.02.2012 05:15

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

С очевидной оговоркой, что значения частичных центов (например, 0,015 доллара США) не могут быть представлены вообще. Разумное ограничение для большинства приложений.

rocketmonkeys 07.12.2010 00:59

Простое решение: храните его в тысячах центов .. Я храню данные в миллионных долях рассматриваемой валюты ..

Marenz 18.08.2011 15:58

Проверьте свое переполнение. Миллионная доля центов перетекает чуть более 20 миллиардов долларов. Тысячи центов при 20 триллионах (что может быть или неприемлемо), а центов - 20 квадриллионов (что я считаю безопасным).

Joshua 18.08.2011 19:05

@Marenz: на любом данном этапе расчета часто можно определить единицу минимального размера, на которой будет выполняться расчет, и не иметь ошибок округления любой величины, возникающих в любых точках, кроме тех случаев, когда вещи явно округлый. Если кто-то покупает пять тысяч чего-то по цене 3 за 1 доллар, общая цена обычно должна составлять 1666,67 доллара (5000/3, с округлением до пенни), а не 1666,66667 долларов (5000/3, с округлением до 1/1000 пенни) или 1666,65 доллара (0,33333). раз 5000).

supercat 05.06.2012 02:03

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

Я предполагаю, что вы используете ROUND глагол в своих процедурах?

Gerhard Weiss 10.02.2009 05:41

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

George 11.02.2009 15:47

Немного предыстории ....

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

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

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

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

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

Из 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;
}

Ради этого я скопировал приведенный выше код и запустил его в кодовой панели. codepad.org/03hAQZwq Это включает вывод.

Dominic K 24.10.2010 00:22

Это фото отвечает:

photo1,C.O.

Это другая ситуация: Человек из Нортгемптона получил письмо, в котором говорилось, что его дом будет конфискован, если он не заплатит ноль долларов и ноль центов!

photo2,C.O.

Это меня рассмешило. Лучшая покупка.

LittleBobbyTables - Au Revoir 26.05.2011 18:36

Ежемесячно в течение года я получал от телефонной компании счет на 0,01 доллара. Так что я заплатил им 0,02 доллара онлайн, затем получил счет на 0,01 доллара за шесть месяцев, и это прекратилось.

Neil McGuigan 04.07.2012 09:18

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

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

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

    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

Другие вопросы по теме