Використовувати плаваючу чи десяткову для обліку суми в доларах програми?


75

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

Яка ваша рекомендація?

Чи слід використовувати тип даних Float або Decimal для доларових сум?

Які є плюси та мінуси для обох?

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

Інший мінус - усі дисплеї, а надруковані суми повинні мати виписку формату, яка відображає два десяткові знаки. Кілька разів я помітив, що цього не було зроблено, і суми не виглядали правильно. (тобто 10,2 або 10,2546)

Професіонал Float займає лише 8 байт на диску, де десяткове число займає 9 байт (десяткове 12,2)


5
Поверніться назад і позбудьтесь своїх поплавців.
Loren Pechtel

Відповіді:


110

Чи слід використовувати тип даних 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), щоб ми могли записати звинувачення в «мільярдної копійки».

Не допомагає проти цієї помилки. Оскільки всі люди автоматично припускають, що комп’ютер правильно підсумовує, практично ніхто не перевіряє самостійно.


Але обов’язково використовуйте принаймні 4 знаки після коми в десятковому полі, якщо ви хочете робити обчислення на ньому, особливо ділення.
HLGEM

1
І обов’язково знайте, що (за замовчуванням) $ 0,045 раундів до $ 0,04 і $ 0,055 раундів до $ 0,06
Кіт,

7
Для тих, хто не знає, що означає Кіт, десяткові типи використовують інший тип округлення. Здається, це зазвичай називають "округленням банкірів", але Вікіпедія має ряд альтернативних назв: округлене від половини до парного, неупереджене округлення, конвергентне округлення, округлення статистиків, голландське округлення, гауссове округлення або округлення банкірів ( en.wikipedia.org / wiki /… ).
patridge

2
Ще слід пам’ятати, що Decimal.Round і String.Format дають різні результати: Decimal.Round (0,045M, 2) = 0,04 і String.Format ("{0: 0,00}", 0,045M) = 0,05
Джейсон Massey

46

3
Це мене розсміяло. Шлях, найкраща покупка.
LittleBobbyTables - Au Revoir

17
Щороку щомісяця я отримував від телефонної компанії рахунок за 0,01 долара. Тож я заплатив їм 0,02 долара в Інтернеті, потім отримав рахунок - 0,01 долара за півроку, потім він зупинився.
Neil McGuigan

22

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

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


1
Хоча дізнатися більше про плаваючу крапку корисно, використання десяткового типу в C # схоже на використання пакета чисел із фіксованою крапкою / довільною точністю, як ви пропонуєте, вбудованого в мову. Див. Msdn.microsoft.com/en-us/library/system.decimal.aspx для пояснення того, як десятковий зберігає точні степені 10 з десятковими знаками замість степенів 2 для десяткового компонента (це в основному int з компонентом десяткового розміщення) ).
Кріс Москіні,

1
"викрити швидкий x87 fp, який зараз майже застарів", це просто неправдиві числа з плаваючою комою, як і раніше є одним із найбільш часто використовуваних типів даних на комп'ютерах, наприклад, симуляції, ігри, обробка сигналів ...
markmnl

10

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


7

Запитайте у своїх бухгалтерів! Вони насупляться на вас за використання поплавця. Як і раніше опублікований, використовуйте float ТІЛЬКИ, якщо ви не дбаєте про точність. Хоча я завжди був би проти, коли справа стосується грошей.

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


6

Плаваючі крапки мають несподівані ірраціональні числа.

Наприклад, ви не можете зберігати 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 знаків після коми.

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

Однак десяткові крапки не дуже добре підходять для будь-якої математики - наприклад, немає вбудованої підтримки десяткових степенів.


3
"ірраціональний" - це не те слово, яке ви шукаєте. 1/3 все ще раціональний, але він не має кінцевого двійкового подання ...
Брайан Постов

Так, я знаю - я просто не впевнений, як ще це назвати: число, яке неможливо представити, занадто багатослівне.
Кіт,

Вони є наближеннями, але тоді цифри, які можна було б представити, також можна наблизити. Фактичним ірраціональним числом є число, яке не може бути представлене будь-якою цілою часткою, незалежно від бази. Це числа, які можна представити в основі 10, але не можуть у основі 2.
Кіт,

Число з незакінчуваним десятковим поданням - це занадто багатослівно!
Кенні Евітт,

Можливо, ви могли б сказати, що числа з плаваючою комою, як правило, зберігають несподівані і неактуальні дробові значення.
Кенні Евітт,

5

Використовуйте десятковий тип сервера SQL .

Не використовуйте гроші та не плавайте .

гроші використовують 4 знаки після коми, це швидше, ніж використання десяткової, АЛЕ страждає від явних і деяких не дуже очевидних проблем із округленням ( див. цю проблему з підключенням )


Дивіться відповідь @David Thornley. Це може бути , що гроші типу найближче відтворює правила обліку, проте (в) приблизно вони.
Рой Тінкер,

5

Я б порадив використовувати 64-розрядні цілі числа, які зберігають все це в копійках.


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

Просте рішення: зберігати його у тисячах центів .. Я зберігаю речі в мільйонних
частках

1
Перевірте своє переповнення. Мільйонні центи перевищують трохи більше 20 мільярдів доларів. Тисячі центів у 20 трильйонів (що може бути або не прийнятно), тоді як центи становлять 20 квадрильйонів (що я вважаю безпечним).
Джошуа

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

5

Трохи передісторії тут ....

Жодна система числення не може точно обробляти всі реальні числа. Усі вони мають свої обмеження, і це включає як стандартну плаваючу крапку IEEE, так і десятковий знак із підписом. Плаваюча крапка IEEE є більш точною на кожен використаний біт, але це тут не має значення.

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

Тому для фінансових розрахунків правильною відповіддю є те, що дає таку ж відповідь, як CPA, який добре володіє арифметикою. Це десяткова арифметика, а не плаваюча точка IEEE.


4

Єдина причина використовувати Float для грошей - це якщо ви не дбаєте про точні відповіді.


4

Плаваючі символи не є точними поданнями, можливі проблеми з точністю, наприклад при додаванні дуже великих і дуже малих значень. Ось чому для валюти рекомендуються десяткові типи, хоча питання точності можуть бути досить рідкісними.

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


Десяткові знаки також не є точними, якщо вони не мають нескінченної точності.
1800 ІНФОРМАЦІЯ

0.1 може зберігатися точно в десятковому полі. Десяткові знаки не є точними для кожного числа , але є точними для більшості (деяких?) Загальних грошових сум. Іноді.
rocketmonkeys

4

Що стосується банківської системи, яку я допоміг розробити, я відповідав за частину системи "нарахування процентів". Кожен день мій код підраховував, скільки відсотків було нараховано (зароблено) на залишок у той день.

Для цього розрахунку була потрібна надзвичайна точність та вірність (ми використовували FLOAT Oracle), щоб ми могли записати нараховані "мільярдні копійки".

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

Це задовольнило бухгалтерів, аудиторів та тестувальників.

Тож уточнюйте у своїх клієнтів. Вони розкажуть вам про свої банківські / бухгалтерські правила та практики.


Мільярдні частки копійки становлять 0,01 ^ е-9 - немає абсолютно ніяких підстав використовувати тут FLOAT Oracle для "надзвичайної точності та вірності", оскільки це подання з плаваючою точкою, яке є приблизним числом, а не точним числом. DECIMAL (38,18) TSQL був би точнішим. Без пояснення того, як ви працювали з мультивалютною валютою, я сумніваюся, що у вас немає помилок. Якщо тестери конвертували з євро в долар Зімбабве, вони могли б побачити справжню проблему округлення.
Джон Заброські

Тільки для уточнення, я використовував плаваючі операції для нарахування процентів. Десяткові знаки використовувались для фактичних операцій (коли виплачувались нараховані відсотки). На той час система була єдиною валютою. Якби я знову мав свій час, мабуть, не використовував би поплавців. :)
Гай,

3

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

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

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


3
Ви повинні зробити це, якщо у вас немає хорошого типу фіксованої точки. Перевагою є те, що ви можете визначити, де десятковий знак, недоліком є ​​те, що ви збираєтеся його зіпсувати. Якщо ви можете отримати тип фіксованої точки, вам не доведеться про це турбуватися.
Michael Kohne

3

Інша річ, про яку ви повинні знати в системах бухгалтерського обліку, - це те, що ніхто не повинен мати прямого доступу до таблиць. Це означає, що весь доступ до системи бухгалтерського обліку повинен здійснюватися через збережені документи. Це запобігає шахрайству, а не лише атакам ін'єкції SQl. Внутрішній користувач, який хоче вчинити шахрайство, не повинен мати можливості безпосередньо змінювати дані в таблицях бази даних. Це критичний внутрішній контроль у вашій системі. Ви дійсно хочете, щоб якийсь незадоволений співробітник перейшов до серверної бази вашої бази даних і почав записувати його чеками? Або приховати, що вони затвердили витрати неавторизованому продавцю, коли вони не мають повноважень щодо затвердження? Лише двоє людей у ​​всій вашій організації повинні мати прямий доступ до даних у вашій фінансовій базі даних, вашому базі даних та його резервній копії. Якщо у вас багато баз даних, лише два з них повинні мати такий доступ.

Я згадую це, тому що якщо ваші програмісти використовували плаваючу систему бухгалтерського обліку, швидше за все, вони абсолютно не знайомі з ідеєю внутрішнього контролю і не враховували їх у своїх зусиллях з програмування.



2

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


Я припускаю, що ви використовуєте в своїх процедурах дієслово ROUND?
Герхард Вайс

Якщо ви маєте на увазі сторону SQL, тоді НІ. Я вважаю за краще, щоб DAL повертав ціле число, як у БД. Саме на рівні бізнес-логіки я роблю трансформацію. int центи = значення% 100; int долари = (вартість - центи) / 100; У .NET 3.5 у мене є метод розширення для цього.
Джордж

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

Заради цього я скопіював наведений вище код і запустив його в кодовій панелі. codepad.org/03hAQZwq Сюди входить результат.
Домінік К

1

Чи роздумували ви про використання типу даних грошей для зберігання доларових сум?

Щодо Con, що десятковий забирає ще один байт, я б сказав, що йому все одно. З 1 мільйона рядків ви будете використовувати лише ще 1 МБ, а пам’ять сьогодні дуже дешева.


1
Не використовуйте тип даних грошей. (Це похмілля від SyBase.)
Мітч Пшениця

1

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


1

Можливо, вам захочеться використовувати якусь форму подання з фіксованою точкою для валютних значень. Ви також хочете дослідити округлення Банкіра (також відоме як "округлення навпіл".) Це дозволяє уникнути упереджень, які існують у звичайному методі "округлення наполовину".


0

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


0

Числа з плаваючою точкою можуть представляти лише числа, які є сумою від’ємних кратних базису - для двійкових плаваючих крапок, звичайно, це два.

Є лише чотири десяткові дроби, які можна точно представити у двійковій системі з плаваючою комою: 0, 0,25, 0,5 та 0,75. Все інше є наближенням, так само, як 0,3333 ... це наближення для 1/3 в десятковій арифметиці.

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


0

Це відмінна стаття, що описує, коли слід використовувати плаваючу та десяткову цифри . 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

0

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

У вас є типи даних валюти ( money, smallmoney), які слід використовувати замість плаваючої або реальної. Зберігання десяткових значень (12,2) усуне ваші округлення, але також усуне їх під час проміжних кроків - що насправді зовсім не те, що вам потрібно в фінансовій програмі.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.