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

StackOverflow https://stackoverflow.com/questions/61872

Вопрос

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

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

Следует ли использовать тип данных с плавающей запятой или Десятичный тип данных для сумм в долларах?

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

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

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

Плюсом является то, что число с плавающей запятой занимает всего 8 байт на диске, тогда как десятичное число занимало бы 9 байт (десятичное число 12,2).

Это было полезно?

Решение

Следует ли использовать тип данных с плавающей запятой или Десятичный тип данных для сумм в долларах?

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

Поплавки были в соответствии с 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), чтобы мы могли записать начисленные "миллиардные доли пенни".

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

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

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

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

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

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

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

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

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

Значения с плавающей точкой фактически хранятся в виде двоичного значения и степени 2 экспоненты.

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

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

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

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

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

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

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

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

Используйте SQL server десятичная дробь Тип.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Из 100 дробей n/100, где n - натуральное число, такое, что 0 <= n и n < 100, только четыре могут быть представлены в виде чисел с плавающей запятой.Взгляните на выходные данные этой программы на языке Си:

#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-data для хранения сумм в долларах?

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

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

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

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

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

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

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

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

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

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

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

Вот интересный пример, который показывает, что как float, так и decimal способны терять точность.При добавлении числа, которое не является целым числом, а затем вычитании этого же числа 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
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top