Когда следует использовать double вместо десятичного?

Я могу назвать три преимущества использования double (или float ) вместо decimal :

  1. Использует меньше памяти.
  2. Быстрее, потому что математические операции с плавающей запятой поддерживаются процессорами.
  3. Может представлять больший диапазон чисел.

Но эти преимущества, по-видимому, применимы только к интенсивным вычислениям, таким как те, которые содержатся в программном обеспечении моделирования. Разумеется, удвоения не следует использовать, когда требуется точность, например, финансовые расчеты. Так есть ли какие-либо практические причины когда-либо выбирать double (или float ) вместо decimal в «нормальных» приложениях?

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

Еще один вопрос: несколько человек указали, что удвоения могут более точно представлять действительные числа. Когда объявлено, я думаю, что они обычно более точно представляют их. Но является ли это истинным утверждением, что точность может уменьшаться (иногда значительно) при выполнении операций с плавающей запятой?

Думаю, вы довольно хорошо обобщили преимущества. Однако вам не хватает одной точки. decimal тип является более точным при отображении базовых 10 чисел (например, используемых в валютных / финансовых расчетах). В общем, double тип будет предлагать по крайней мере такую ​​же высокую точность (кто-то исправит меня, если я ошибаюсь) и определенно большую скорость для произвольных действительных чисел. Простой вывод: при рассмотрении того, что использовать, всегда используйте double если вам не нужна base 10 точность, которую предлагает decimal знак.

Редактировать:

Что касается вашего дополнительного вопроса о снижении точности чисел с плавающей запятой после операций, это немного более тонкий вопрос. Действительно, точность (я использую термин взаимозаменяемо для точности здесь) будет неуклонно уменьшаться после каждой операции. Это объясняется двумя причинами:

  1. тот факт, что некоторые числа (наиболее очевидно десятичные) не могут быть действительно представлены в форме с плавающей точкой
  2. ошибки округления, как если бы вы делали расчет вручную. Это зависит в значительной степени от контекста (сколько операций, которые вы выполняете), являются ли эти ошибки достаточно значительными, чтобы оправдать многие мысли.

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

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

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

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

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

РЕДАКТИРОВАТЬ

Согласно справочной документации по десятичным знакам C # :

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

Поэтому, чтобы прояснить мое вышеизложенное утверждение:

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

Я только когда-либо работал в отраслях, где десятичные знаки благоприятны. Если вы работаете с phsyics или графическими движками, вероятно, гораздо более полезно разработать для типа с плавающей точкой (float или double).

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

  • decimal = 28-29 значащих цифр
  • double = 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 

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

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

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

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

Используйте double или float, когда вам не нужна точность, например, в платформерной игре, которую я написал, я использовал float для хранения скорости игрока. Очевидно, мне здесь не нужна сверхточная точность, потому что я, в конце концов, округляюсь до Int для рисования на экране.

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

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

Хотя этот короткий пример не даст кумулятивной ошибки с использованием двойного или десятичного числа, довольно легко показать случаи, когда простое вычисление двойного или десятичного числа и перенос будет накапливать значительную ошибку. Если правила, в которых вы работаете, требуют ограниченного количества десятичных знаков, сохраняя каждое значение как целое число, умножая на 10 ^ (требуемое число из десятичного разряда), а затем делясь на 10 ^ (требуемое число из десятичных знаков), чтобы получить фактическое значение будет избегать кумулятивной ошибки.

В ситуациях, когда фракции пенни не встречаются (например, торговый автомат), нет оснований использовать неинтегральные типы вообще. Просто подумайте об этом, считая гроши, а не доллары. Я видел код, где каждый расчет включал только целые гроши, но двойное использование привело к ошибкам! Целочисленная только математика устранила проблему. Поэтому мой нетрадиционный ответ, когда это возможно, претерпевает двойной и десятичный.

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

Примечание. Этот пост основан на информации о возможностях десятичного типа от http://csharpindepth.com/Articles/General/Decimal.aspx и моей собственной интерпретации того, что это значит. Я предполагаю, что Double – это обычная двойная точность IEEE.

Примечание2: наименьшее и наибольшее в этом сообщении ссылается на величину числа.

Плюсы «десятичной».

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

Недостатки десятичных

  • Это будет намного медленнее (у меня нет контрольных показателей, но я бы предположил, что, по крайней мере, на порядок больше, может быть больше), десятичный выигрыш не будет выигрывать от какого-либо аппаратного ускорения, и арифметика на нем потребует относительно дорогого умножения / деления по степеням 10 ( который намного дороже, чем умножение и деление на степени 2), чтобы соответствовать экспоненте до сложения / вычитания и возвращать экспоненту обратно в диапазон после умножения / деления.
  • decimal будет переполняться раньше, чем double. decimal может представлять только числа до ± 2 96 -1. При сравнении double может представлять числа до почти ± 2 1024
  • decimal будет раньше. Наименьшие числа, представляемые в десятичной форме, составляют ± 10 -28 . Посредством сравнения double может представлять значения до 2 -149 (приблизительно 10-45 ), если подснтовые числа поддерживаются и 2 -126 (приблизительно 10 -38 ), если они не являются.
  • decimal занимает вдвое больше памяти, чем double.

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

Используйте плавающие точки, если вы оцениваете производительность по правильности.

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

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

Графический голод? float или double достаточно. Анализ финансовых данных, meteorит, поражающий планету? Это потребует немного точности 🙂

Decimal имеет более широкие байты, double поддерживается процессором. Десятичное значение – base-10, поэтому преобразование десятичного в двойное происходит, когда вычисляется десятичное число.

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

Имейте в виду, что .NET CLR поддерживает только Math.Pow (double, double). Десятичное число не поддерживается.

.NET Framework 4

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

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

Зависит от того, для чего вам это нужно.

Поскольку float и double являются бинарными типами данных, у вас есть некоторые diifculties и ошибки в пути в числах раундов, так что, например, double будет округлять 0,1 до 0,100000001490116, double будет также раундом от 1/3 до 0,333333334326441. Проще говоря, не все реальные числа имеют точное представление в двойных типах

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

Давайте будем гением компьютера.