Когда я должен использовать двойной вместо десятичного? - PullRequest
252 голосов
/ 29 апреля 2009

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

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

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

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

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

Ответы [ 12 ]

296 голосов
/ 29 апреля 2009

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

Edit:

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

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

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

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

56 голосов
/ 29 апреля 2009

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

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

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

EDIT

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

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

Итак, чтобы уточнить мое приведенное выше утверждение:

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

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

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

  • десятичный = 28-29 значащих цифр
  • double = 15-16 значащих цифр
  • float = 7 значащих цифр

РЕДАКТИРОВАТЬ 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

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

24 голосов
/ 29 апреля 2009

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

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

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

6 голосов
/ 29 апреля 2009

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

4 голосов
/ 31 января 2016

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

Вычисление 1/6 от 100 долл. США дает 16,66666666666666 ..., поэтому значение, приведенное на листе, составит 16,666667 долл. США. И double, и decimal должны давать этот результат с точностью до 6 знаков после запятой. Однако мы можем избежать любой кумулятивной ошибки, передав результат вперед как целое число 16666667. Каждое последующее вычисление может быть выполнено с той же точностью и перенесено аналогично. Продолжая пример, я рассчитываю налог с продаж в Техасе на эту сумму (16666667 * .0825 = 1375000). Добавление двух (это короткий рабочий лист) 1666667 + 1375000 = 18041667. Перемещение десятичной точки обратно дает нам 18.041667, или 18.04 $.

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

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

3 голосов
/ 29 апреля 2009

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

2 голосов
/ 22 марта 2016

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

Примечание 2: наименьшее и наибольшее в этом посте - это величина числа.

Плюсы "десятичного числа".

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

Минусы десятичных

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

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

0 голосов
/ 07 ноября 2017

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

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

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

0 голосов
/ 18 августа 2015

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

0 голосов
/ 18 марта 2014

Десятичное число имеет более широкие байты, двойное изначально поддерживается процессором. Десятичное число - это основание-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);
...