2009-04-29 4 views
228

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

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

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

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

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

+0

см. Также http://stackoverflow.com/questions/2545567/in-net-how-do-i-choose-between-a-decimal-and-a-double –

+3

Это довольно регулярно поддерживается, и я все еще боюсь с этим. Например, я работаю над приложением, которое выполняет финансовые расчеты, поэтому я использую десятичное целое. Но Math и VisualBasic. Финансовые функции используют double, поэтому есть много конверсий, из-за которых я постоянно размышляю над использованием десятичной дроби. –

+0

@JamieIde, это безумие Финансовые функции используют double, деньги всегда должны быть десятичными. –

ответ

273

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

Edit:

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

Для получения более подробного обзора конкретных случаев, когда ошибки могут быть введены, см. Раздел «Точность» в Wikipedia article. Наконец, если вы хотите серьезно углубленное (и математическое) обсуждение чисел/операций с плавающей запятой на уровне машины, попробуйте прочитать часто цитируемую статью What Every Computer Scientist Should Know About Floating-Point Arithmetic.

+1

Можете ли вы предоставить пример, e базового номера 10, с которым точность теряется при преобразовании в базу 2? –

+0

@Mark: 1.000001 - один пример, по крайней мере, согласно Джону Скиту. (См. Вопрос 3 на этой странице: http://www.yoda.arachsys.com/csharp/teasers-answers.html) – Noldorin

+18

@Mark: очень простой пример: 0.1 - это периодическая дробь в базе 2, поэтому она не может быть точно определена в «двойном». Современные компьютеры все равно будут печатать правильное значение, но только потому, что они «угадывают» результат - не потому, что это действительно правильно выражено. –

6

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

+3

Прецизионность, являющаяся ТОЛЬКО преимуществом десятичных знаков, это правильно. Вы не должны спрашивать, когда вы должны использовать числа с плавающей запятой по десятичным знакам. Это должна быть ваша первая мысль. Тогда возникает вопрос, когда вы должны использовать десятичные знаки (и ответ прямо здесь ... когда имеет значение точность). –

+2

@ Даниэль Прямо, это смешно, но у меня противоположное мнение. Я думаю, что использование менее точного типа из-за его характеристик производительности сводится к преоптимизации. Вы, вероятно, придется заплатить за эту преоптимизацию много раз, прежде чем осознать свою выгоду. –

+3

@ Майкл Медоуз, я могу понять этот аргумент. Следует отметить, что одна из основных жалоб с преждевременной оптимизацией заключается в том, что программисты не склонны знать, что будет медленным. Мы знаем, без сомнения, что десятичные знаки медленнее, чем удваиваются. Тем не менее, я полагаю, что в большинстве случаев улучшение производительности не будет заметно для пользователя. Конечно, в большинстве случаев точность также не нужна. Хех. –

50

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

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

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

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

огласно к reference documentation on C# decimals:

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

Так прояснить мою выше заявление:

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

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

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

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

EDIT 2

В ответ на комментарий Konrad Rudolph, элемент № 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. Часто эта проблема не проявляется, потому что положительные неточности уравновешиваются с отрицательной неточностью, и они стирают в совокупности, но агрегация того же числа (как я сделал в примере) подчеркивает эту проблему. –

+0

Отличный пример. Просто показали это моим младшим разработчикам, дети были поражены. – Machado

3

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

0

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

+6

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

0

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

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

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

+8

Десятичные числа также являются оценками. Они соответствуют соглашениям финансовой арифметики, но нет никаких преимуществ, например, в расчетах с участием физики. –

21

Используйте десятичное значение для значений оснований 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; 

Так снова беря пример портфеля:

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

  • Вес каждой строки в портфеле (= Рыночная стоимость/СУММ (Рыночная стоимость)) обычно лучше представлен как двойной.

+1

+1 Простой и легкий пример! –

-1

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

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

4

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

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

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

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

2

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

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

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

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

Против десятичного

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

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

0

Зависит от того, в чем вы нуждаетесь.

Поскольку поплавок и двойной являются двоичными типами данных у вас есть некоторых diifculties и errrors на пути в цифрах раундов, так, например, двойной бы округлить 0,1 до 0.100000001490116 дважды будет также круглый 1/3 до +0,33333334326441. Проще говоря, не все реальные числа имеют точное представление в двойных типах.

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