Коли я повинен використовувати подвійний замість десяткових?


265

Я можу назвати три переваги використання double(або float) замість decimal:

  1. Використовує менше пам'яті.
  2. Швидше, оскільки математичні операції з плаваючою комою підтримуються процесорами.
  3. Може представляти більший діапазон чисел.

Але ці переваги, здається, застосовуються лише до обчислювальних інтенсивних операцій, таких як ті, які знайдені в програмі моделювання. Звичайно, парні не слід використовувати, коли потрібна точність, наприклад, фінансові розрахунки. Чи є якісь практичні причини коли-небудь вибирати double(або float) замість decimal"звичайних" додатків?

Відредаговано, щоб додати: Дякую за всі чудові відповіді, я дізнався від них.

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



5
Це досить часто відкликається, і я все ще борюся з цим. Наприклад, я працюю над додатком, який виконує фінансові розрахунки, тому я використовую десятковий протягом усього. Але функції Math та VisualBasic.Financial використовують подвійне, тому існує багато перетворень, які мені постійно вдруге здогадуються про використання десяткової.
Джеймі Іде

@JamieIde божевільний, що фінансові функції використовують подвійно, гроші завжди повинні бути десяткові.
Кріс Марісіч

@ChrisMarisic Але що може зробити jamie Ide, працюючи зі спадщиною, за допомогою подвійного? Тоді вам слід скористатися і подвійним, і безліч конверсій спричинить помилки округлення ... не дивно, що він згадав VisualBasic pfffhh .....
Elisabeth

@Elisabeth я, швидше за все, використовуватиму іншу бібліотеку, яка належним чином підтримує десяткові. Що б не було, можливо, існує VisualBasic.Financial в багатьох інших бібліотеках сьогодні
Кріс Марісіч,

Відповіді:


306

Я думаю, ви досить добре підсумували переваги. Однак ви не вистачаєте однієї точки. decimalТип тільки більш точний на представляє підставу 10 числа (наприклад , ті , які використовуються в валюті / фінансових розрахунках). Загалом, doubleтип запропонує принаймні велику точність (хтось мене виправить, якщо я помиляюся) і, безумовно, більшу швидкість для довільних реальних чисел. Простий висновок такий: коли ви розглядаєте, що використовувати, завжди використовуйте, doubleякщо вам не потрібна base 10точність, яку decimalпропонують.

Редагувати:

Що стосується вашого додаткового запитання про зниження точності чисел з плаваючою комою після операцій, це дещо тонке питання. Дійсно, точність (тут я використовую термін взаємозамінно для точності тут) буде постійно знижуватися після кожної операції. Це пов'язано з двома причинами:

  1. той факт, що певні числа (найбільш очевидно десятичні) не можуть бути справді представлені у формі з плаваючою комою
  2. Помилки округлення трапляються так само, як якщо б ви робили обчислення вручну. Багато в чому залежить від контексту (скільки операцій ви виконуєте), чи є ці помилки достатньо вагомими, щоб все-таки задуматися.

У всіх випадках, якщо ви хочете порівняти два числа з плаваючою комою, які теоретично повинні бути еквівалентними (але були отримані при використанні різних обчислень), вам потрібно дозволити певний ступінь допуску (скільки змінюється, але, як правило, дуже мало) .

Для більш детального огляду конкретних випадків, коли можна вводити помилки в точності, дивіться розділ «Точність» статті Вікіпедії . Нарешті, якщо ви хочете серйозно поглибити (і математично) обговорення чисел / операцій з плаваючою комою на машинному рівні, спробуйте прочитати статтю, що цитується, що повинен знати кожен арифметик з плаваючою комою .


1
Чи можете ви надати іспит, e базового числа 10, з яким втрачається точність при переході на базу 2?
Марк Сідаде

@Mark: 1.000001 - один із прикладів, принаймні за словами Джона Скіта. (Див питання 3 цієї сторінки: yoda.arachsys.com/csharp/teasers-answers.html )
нолдорін

25
@ Марк: дуже простий приклад: 0,1 - періодична частка в базі 2, тому вона не може бути виражена точно в a double. Сучасні комп'ютери все одно будуть друкувати правильне значення, але лише тому, що вони «здогадуються» в результаті - не тому, що воно справді виражається правильно.
Конрад Рудольф

1
DecimalТип має 93-біт точності в мантиси, по порівнянні з близько 52 для double. Я хотів би, щоб Microsoft підтримувала 80-розрядний формат IEEE, хоча навіть якщо його потрібно було викласти до 16 байт; це дозволило б забезпечити більший діапазон, ніж doubleабо Decimal, набагато кращу швидкість, ніж Decimalпідтримка трансцендентальних операцій (наприклад, sin (x), log (x) тощо), і точність, яка, хоча і не настільки хороша, як це Decimalбуло б набагато краще, ніж double.
supercat

@charlotte: Якщо ви прочитаєте мою повну публікацію, ви побачите, що це пояснено.
Нолдорін

59

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

Загалом, якщо ваше значення є тимчасовим (не використовується повторно), ви можете використовувати тип плаваючої точки. Справжня проблема типів з плаваючою комою - це наступні три сценарії.

  1. Ви агрегуєте значення з плаваючою комою (у такому випадку сполуки помилок точності)
  2. Ви будуєте значення на основі значення з плаваючою комою (наприклад, в рекурсивному алгоритмі)
  3. Ви займаєтесь математикою з дуже великою кількістю значущих цифр (наприклад, 123456789.1 * .000000000000000987654321)

EDIT

Відповідно до довідкової документації на десяткові числа C # :

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

Отже, щоб уточнити моє вище твердження:

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

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

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

  • десяткова = 28-29 значущих цифр
  • подвійний = 15-16 значущих цифр
  • float = 7 значущих цифр

EDIT 2

У відповідь на коментар Конрада Рудольфа пункт №1 (вище), безумовно, правильний. Агрегація неточності дійсно складна. Дивіться приклад нижче:

private const float THREE_FIFTHS = 3f / 5f;
private const int ONE_MILLION = 1000000;

public static void Main(string[] args)
{
    Console.WriteLine("Three Fifths: {0}", THREE_FIFTHS.ToString("F10"));
    float asSingle = 0f;
    double asDouble = 0d;
    decimal asDecimal = 0M;

    for (int i = 0; i < ONE_MILLION; i++)
    {
        asSingle += THREE_FIFTHS;
        asDouble += THREE_FIFTHS;
        asDecimal += (decimal) THREE_FIFTHS;
    }
    Console.WriteLine("Six Hundred Thousand: {0:F10}", THREE_FIFTHS * ONE_MILLION);
    Console.WriteLine("Single: {0}", asSingle.ToString("F10"));
    Console.WriteLine("Double: {0}", asDouble.ToString("F10"));
    Console.WriteLine("Decimal: {0}", asDecimal.ToString("F10"));
    Console.ReadLine();
}

Це виводить наступне:

Three Fifths: 0.6000000000
Six Hundred Thousand: 600000.0000000000
Single: 599093.4000000000
Double: 599999.9999886850
Decimal: 600000.0000000000

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


1
Пункт 1 неправильний. Помилки точності / округлення трапляються лише в литті, а не в розрахунках. Це є , звичайно , правильно , що більшість математичних операцій нестійкі, таким чином , помноживши цю помилку. Але це вже інше питання, і воно застосовується однаково для всіх типів даних з обмеженою точністю, зокрема, для десяткової.
Конрад Рудольф

1
@Konrad Rudolph, див. Приклад у "EDIT 2" як доказ того, що я намагався зробити у пункті № 1. Часто ця проблема не проявляється, оскільки позитивна неточність врівноважується з негативною неточністю, і вони миються сукупність, але сукупність тієї ж кількості (як я робила в прикладі) висвітлює проблему.
Майкл Медоуз

Чудовий приклад. Щойно показав це моїм молодшим розробникам, діти були вражені.
Мачадо

Тепер ви можете зробити те ж саме з 2 / 3рс замість 3 / 5т ... Ви повинні дізнатися про статеву систему максимальної чисельності, яка обробляє 2 / 3рс ідеально.
gnasher729

1
@ gnasher729, використовуючи 2 / 3rds замість 3 / 5th, не вдавалося прекрасно обробляти для різних типів. Цікаво, що значення поплавця поступалось, Single: 667660.400000000000а десяткове значення поступалось Decimal: 666666.7000000000. Значення поплавця трохи менше однієї тисячі над правильним значенням.
jhenninger

25

Використовуйте десятковий для базових значень 10, наприклад фінансові розрахунки, як пропонують інші.

Але подвійна, як правило, більш точна для довільних обчислених значень.

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

У наступному прикладі doubleResult ближче до 1, ніж decimalResult:

// Add one third + one third + one third with decimal
decimal decimalValue = 1M / 3M;
decimal decimalResult = decimalValue + decimalValue + decimalValue;
// Add one third + one third + one third with double
double doubleValue = 1D / 3D;
double doubleResult = doubleValue + doubleValue + doubleValue;

Тож знову взявши приклад портфоліо:

  • Ринкова вартість кожного рядка в портфелі є грошовою вартістю і, ймовірно, найкраще представлена ​​у вигляді десяткової.

  • Вага кожного рядка в портфелі (= Ринкова вартість / SUM (Ринкова вартість)) зазвичай краще представити як подвійний.


6

Використовуйте дубль або поплавок, коли вам не потрібна точність, наприклад, у платформенній грі, яку я написав, я використовував поплавок для зберігання швидкості програвача. Очевидно, мені тут не потрібна надточна точність, оскільки я врешті-решт перейшов до Int для малювання на екрані.


3
Точність є ТІЛЬКОю перевагою десяткових знаків, це правильно. Ви не повинні запитувати, коли слід використовувати числа з плаваючою комою над десятковими знаками. Це має бути вашою першою думкою. Тоді питання полягає в тому, коли вам слід використовувати десяткові знаки (і відповідь тут є прямо ... коли точність має значення).
Мисливець на екземпляр

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

3
@Michael Meadows, я можу зрозуміти цей аргумент. Щось слід зазначити, що одна з головних скарг на передчасну оптимізацію полягає в тому, що програмісти не схильні знати, що буде повільно. Ми, без сумніву, знаємо, що десятковий знак проходить повільніше, ніж удвічі. Тим не менш, я вважаю, що в більшості випадків покращення продуктивності все одно не буде помітно користувачеві. Звичайно, в більшості випадків точність теж не потрібна. Хе.
Мисливець на екземпляр

Десяткова плаваюча точка фактично МЕНШЕ точніша, ніж двійкова плаваюча точка з використанням однакової кількості бітів. Перевага Десяткових полягає в тому, що можна точно представляти ДЕКІМАЛЬНІ дроби, як 0,01, які є загальними у фінансовому розрахунку.
dan04

Ну, це не зовсім правильно :) - у багатьох іграх цифри з плаваючою комою можуть бути небажаними через те, що вони не є послідовними. Дивіться тут
BlueRaja - Danny Pflughoeft

4

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

Розрахунок 1/6 з 100 доларів США приносить $ 16.66666666666666 ..., тому значення, проведене на робочому аркуші, складе $ 16.666667. Як подвійні, так і десяткові повинні отримувати результат точно до 6 знаків після коми. Однак ми можемо уникнути будь-якої сукупної помилки, перенісши результат вперед як ціле число 16666667. Кожен наступний обчислення може бути зроблений з однаковою точністю і перенесений аналогічно. Продовжуючи приклад, я обчислюю Техаський податок з продажу на цю суму (16666667 * .0825 = 1375000). Додавання двох (це короткий робочий аркуш) 1666667 + 1375000 = 18041667. Переміщення десяткової крапки назад дає нам 18.041667, або 18,04 дол.

Хоча цей короткий приклад не призведе до накопичувальної помилки, використовуючи подвійну чи десяткову, досить легко показати випадки, коли просто обчислення подвійного чи десяткового чи перенесення вперед накопичувало б значну помилку. Якщо правила, для яких ви працюєте, вимагають обмеженої кількості десяткових знаків, зберігаючи кожне значення як ціле число, множуючи на 10 ^ (потрібно # десяткового знаку), а потім ділити на 10 ^ (потрібно # десяткових знаків), щоб отримати фактичне Значення дозволить уникнути будь-якої сукупної помилки.

У ситуаціях, коли фракції грошей не трапляються (наприклад, торговий автомат), немає жодного приводу використовувати неінтегральні типи. Просто подумайте про це як підрахунок копійок, а не доларів. Я бачив код, де кожен розрахунок передбачав лише цілі копійки, але використання подвійного призвело до помилок! Проблема видалила лише математика із цілого числа. Тож моя нетрадиційна відповідь, коли це можливо, пробує як подвійну, так і десяткову.


3

Якщо вам потрібно двійкове перепитування з іншими мовами або платформами, можливо, вам знадобиться використовувати float або double, які є стандартизованими.


2

Примітка. Ця публікація заснована на інформації про можливості десяткового типу з http://csharpindepth.com/Articles/General/Decimal.aspx та моїй власній інтерпретації того, що це означає. Я вважаю, що Double - це нормальна IEEE подвійна точність.

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

Плюси "десяткових".

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

Мінуси десяткових

  • Це буде набагато повільніше (у мене немає орієнтирів, але я б припустив, щонайменше на порядок, можливо, більше), десятковий не матиме користі від будь-якого апаратного прискорення, а арифметика на ньому вимагатиме відносно дорогого множення / ділення на потужності 10 ( що набагато дорожче, ніж множення і ділення на сили 2), щоб співставити показник до додавання / віднімання і повернути показник в діапазон після множення / ділення.
  • десяткова переливається раніше, ніж подвійна. десяткові можуть представляти числа лише до ± 2 96 -1. За допомогою порівняння подвійний може представляти числа приблизно до ± 2 1024
  • Десяткова затікатиме раніше. Найменші числа, які можна представити у десятковій формі, становлять ± 10 -28 . За допомогою порівняння подвійний може представляти значення до 2 -149 (приблизно 10 -45 ), якщо підтримуються суббромні числа, і 2 -126 (приблизно 10 -38 ), якщо їх немає.
  • десяткова займає вдвічі більше пам’яті, ніж подвійна.

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


2

Залежить від того, для чого вам це потрібно.

Оскільки float і double - це бінарні типи даних, у вас є деякі труднощі та помилки в дорозі в числах раундів, тому, наприклад, подвійний би округляв 0,1 до 0,100000001490116, подвійний також би округлював 1/3 до 0,33333334326441. Простіше кажучи, не всі реальні числа мають точне подання у подвійних типах

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


0

Використовуйте плаваючі точки, якщо ви цінуєте продуктивність за правильність.


6
Десяткові числа не є більш правильними, за винятком деяких обмежених випадків, які іноді (далеко не завжди) важливі.
Девід Торнлі

0

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

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

Графічний голодний? float або double досить. Аналіз фінансових даних, що метеор вражає якусь планету? Їм знадобиться трохи точності :)


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

0

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

For accounting - decimal
For finance - double
For heavy computation - double

Майте на увазі .NET CLR підтримує лише Math.Pow (подвійний, подвійний). Десяткові знаки не підтримуються.

.NET Framework 4

[SecuritySafeCritical]
public static extern double Pow(double x, double y);

0

Подвійні значення за замовчуванням будуть серіалізуватись на наукові позначення, якщо це позначення коротше десятичного відображення. (наприклад, .00000003 буде 3e-8) Десятичні значення ніколи не будуть серіалізуватися на наукові позначення. При серіалізації для споживання зовнішньою стороною це може бути врахуванням.

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