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

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

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

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

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

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


person Jamie Ide    schedule 29.04.2009    source источник
comment
см. также stackoverflow.com/questions/2545567/   -  person Ian Ringrose    schedule 30.03.2010
comment
За него довольно часто голосуют, и я все еще борюсь с этим. Например, я работаю над приложением, которое выполняет финансовые вычисления, поэтому я использую десятичные числа. Но функции Math и VisualBasic.Financial используют double, поэтому существует много преобразований, из-за чего я постоянно сомневаюсь в использовании десятичной дроби.   -  person Jamie Ide    schedule 22.09.2014
comment
@JamieIde, это безумие, финансовые функции используют двойное значение, деньги всегда должны быть в десятичном формате.   -  person Chris Marisic    schedule 22.04.2015
comment
@ChrisMarisic Но что может Джейми Айд, работая с устаревшей хренью, используя double? Тогда вы также должны использовать double, иначе многие преобразования вызовут ошибки округления ... никаких чудес, он упомянул VisualBasic pfffhh .....   -  person Elisabeth    schedule 14.06.2016
comment
@Elisabeth, я бы, вероятно, использовал другую библиотеку, которая правильно поддерживает десятичные числа. Все, что предоставляет VisualBasic.Financial, вероятно, сегодня существует во многих других библиотеках.   -  person Chris Marisic    schedule 14.06.2016


Ответы (12)


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

Изменить:

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

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

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

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

person Noldorin    schedule 29.04.2009
comment
Можете ли вы привести пример e числа с основанием 10, с которым теряется точность при преобразовании в основание 2? - person Mark Cidade; 30.04.2009
comment
@Mark: 1.000001 - один из примеров, по крайней мере, по словам Джона Скита. (См. Вопрос 3 на этой странице: yoda.arachsys.com/csharp/teasers- answers.html) - person Noldorin; 30.04.2009
comment
@Mark: очень простой пример: 0,1 - это периодическая дробь по основанию 2, поэтому ее нельзя точно выразить в double. Современные компьютеры по-прежнему будут печатать правильное значение, но только потому, что они «угадывают» результат, а не потому, что он действительно выражен правильно. - person Konrad Rudolph; 02.05.2009
comment
Тип Decimal имеет 93 бита точности мантиссы по сравнению с 52 для double. Я бы хотел, чтобы Microsoft поддержала 80-битный формат IEEE, даже если бы его нужно было дополнить до 16 байт; это позволило бы больший диапазон, чем double или Decimal, гораздо лучшую скорость, чем Decimal, поддержку трансцендентных операций (например, sin (x), log (x) и т. д.) и точность, которая, хотя и не так хороша, как Decimal, была бы намного лучше, чем double. - person supercat; 03.09.2013
comment
@charlotte: Если вы прочтете мой пост полностью, то увидите, что это объясняется. - person Noldorin; 03.07.2015

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

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

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

ИЗМЕНИТЬ

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

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

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

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

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

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

  • десятичный = 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

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

person Michael Meadows    schedule 29.04.2009
comment
Пункт 1 неверен. Ошибки точности / округления возникают только при отливке, но не при расчетах. Разумеется, верно, что большинство математических операций нестабильно, что увеличивает ошибку. Но это еще одна проблема, и она применима ко всем типам данных ограниченной точности, особенно к десятичным. - person Konrad Rudolph; 02.05.2009
comment
@Konrad Rudolph, см. Пример в EDIT 2 как доказательство того, что я пытался сделать в пункте № 1. Часто эта проблема не проявляется, потому что положительная неточность уравновешивается с отрицательной неточностью, и они стираются в совокупности , но агрегирование того же числа (как и в примере) подчеркивает проблему. - person Michael Meadows; 04.05.2009
comment
Отличный пример. Просто показал его младшим разработчикам, дети были в восторге. - person Machado; 19.01.2012
comment
Теперь вы можете сделать то же самое с 2/3 вместо 3/5 ... Вы должны узнать о шестидесятеричной системе счисления, которая отлично справляется с 2/3. - person gnasher729; 16.05.2015
comment
@ gnasher729, использование 2/3 вместо 3/5 не было обработано идеально для разных типов. Интересно, что значение с плавающей запятой дало Single: 667660.400000000000, а десятичное значение дало Decimal: 666666.7000000000. Значение с плавающей запятой немного меньше, чем на тысячу больше правильного значения. - person jhenninger; 12.06.2015

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

person Joe    schedule 29.04.2009

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

person FlySwat    schedule 29.04.2009
comment
Точность - ЕДИНСТВЕННОЕ преимущество десятичных дробей, и это правильно. Вы не должны спрашивать, когда следует использовать числа с плавающей запятой вместо десятичных. Это должно быть вашей первой мыслью. Тогда возникает вопрос, когда следует использовать десятичные дроби (и ответ здесь ... когда точность имеет значение). - person Instance Hunter; 29.04.2009
comment
@Daniel Straight, Забавно, но у меня противоположное мнение. Я думаю, что использование менее точного типа из-за его рабочих характеристик - это предварительная оптимизация. Возможно, вам придется много раз платить за эту предварительную оптимизацию, прежде чем вы осознаете ее преимущества. - person Michael Meadows; 29.04.2009
comment
@ Майкл Медоуз, я могу понять этот аргумент. Однако следует отметить, что одна из основных жалоб на преждевременную оптимизацию заключается в том, что программисты не склонны знать, что будет медленным. Однако мы без всякого сомнения знаем, что десятичные дроби медленнее, чем удвоенные. Тем не менее, я полагаю, что в большинстве случаев улучшение производительности все равно пользователю не будет заметно. Конечно, в большинстве случаев точность тоже не нужна. Хех. - person Instance Hunter; 29.04.2009
comment
Десятичные числа с плавающей запятой на самом деле МЕНЬШЕ точны, чем двоичные числа с плавающей запятой, использующие то же количество битов. Преимущество Decimal заключается в возможности точно представлять DECIMAL дроби, такие как 0,01, которые распространены в финансовых расчетах. - person dan04; 01.08.2010
comment
Что ж, это не совсем правильно :) - во многих играх числа с плавающей запятой могут быть нежелательными из-за того, что они несовместимы. См. здесь - person BlueRaja - Danny Pflughoeft; 21.07.2011

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

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

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

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

person G DeMasters    schedule 30.01.2016

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

person Will Dean    schedule 29.04.2009

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

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

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

person Neil Meyer    schedule 07.11.2017

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

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

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

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

Минусы десятичной дроби

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

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

person plugwash    schedule 22.03.2016

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

person Mark Brackett    schedule 29.04.2009
comment
Десятичные числа не более правильны, за исключением некоторых ограниченных случаев, которые иногда (отнюдь не всегда) важны. - person David Thornley; 29.04.2009

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

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

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

person Khan    schedule 29.04.2009
comment
Десятичные числа тоже являются оценочными. Они соответствуют правилам финансовой арифметики, но, скажем, в вычислениях с использованием физики нет никаких преимуществ. - person David Thornley; 29.04.2009

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

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

person chris klassen    schedule 17.08.2015