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

265

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

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

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

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

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

Джейми Иде
источник
смотри также stackoverflow.com/questions/2545567/...
Ian Рингроуза
5
За это регулярно голосуют, и я все еще борюсь с этим. Например, я работаю над приложением, которое выполняет финансовые расчеты, поэтому я использую десятичное число во всем. Но функции Math и VisualBasic.Financial используют double, поэтому есть много конверсий, которые заставляют меня постоянно догадываться об использовании десятичной дроби.
Джейми Ид
@JamieIde, это безумие, финансовые функции используют двойные, деньги всегда должны быть в десятичном виде.
Крис Марисик
@ChrisMarisic Но что может Джейми Иде делать с устаревшей хренью, используя double? Тогда вы должны также использовать double, иначе многие преобразования приведут к ошибкам округления ... не удивительно, что он упомянул VisualBasic pfffhh .....
Элизабет
@ Elisabeth Я бы, вероятно, использовал другую библиотеку, которая правильно поддерживала бы десятичную. То, что VisualBasic.Financial предоставляет, вероятно, существует сегодня во многих других библиотеках
Крис Марисик

Ответы:

306

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

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

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

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

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

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

нолдорин
источник
1
Можете ли вы предоставить пример числа 10, с которым теряется точность при преобразовании в базу 2?
Марк Сидаде
@Mark: 1.000001 - один из примеров, по крайней мере, по словам Джона Скита. (См. Вопрос 3 на этой странице: yoda.arachsys.com/csharp/teasers-answers.html )
Нолдорин,
25
@Mark: очень простой пример: 0,1 - это периодическая дробь в базе 2, поэтому она не может быть точно выражена в a double. Современные компьютеры будут по-прежнему печатать правильное значение, но только потому, что они «угадывают» результат, а не потому, что оно действительно выражено правильно.
Конрад Рудольф
1
DecimalТип имеет 93-бит точности в мантиссы, по сравнению с около 52 для double. Хотелось бы, чтобы Microsoft поддерживала 80-битный формат IEEE, даже если он должен был быть дополнен до 16 байтов; он мог бы обеспечить больший диапазон doubleили Decimalгораздо лучшую скорость, чем Decimalподдержку трансцендентных операций (например, sin (x), log (x) и т. д.), и точность, которая хотя и не так хороша, как Decimalбыла бы намного лучше, чем double.
суперкат
@charlotte: Если вы прочитаете мой полный пост, вы увидите, что объяснил.
Нолдорин
59

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

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

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

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

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

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

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

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

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

Десятичное число не является бесконечно точным (невозможно представить бесконечную точность для нецелого в примитивном типе данных), но оно гораздо точнее, чем 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

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

Майкл Медоуз
источник
1
Пункт 1 неверен. Ошибки точности / округления возникают только при приведении, но не при расчетах. Это является , конечно , правильно , что большинство математических операций неустойчивы, таким образом , умножив эту ошибку. Но это другая проблема, и она применяется одинаково для всех типов данных с ограниченной точностью, в частности, для десятичной.
Конрад Рудольф
1
@ Конрад Рудольф, см. Пример в «РЕДАКТИРОВАТЬ 2» как свидетельство того, что я пытался подчеркнуть в пункте № 1. Часто эта проблема не проявляется, потому что положительная неточность уравновешивается отрицательной неточностью, и они стираются в агрегирование, но агрегирование того же числа (как я сделал в примере) подчеркивает проблему.
Майкл Медоус
Отличный пример. Просто показав это моим младшим разработчикам, дети были поражены.
Мачадо
Теперь вы можете сделать то же самое с 2 / 3rds вместо 3 / 5ths ... Вы должны узнать о системе счисления sexagesimal, которая прекрасно обрабатывает 2 / 3rds.
gnasher729
1
@ gnasher729, использование 2/3 вместо 3/5 не обрабатывалось отлично для разных типов. Интересно, что значение с плавающей точкой уступило, Single: 667660.400000000000а десятичное значение уступило Decimal: 666666.7000000000. Значение с плавающей точкой составляет чуть меньше одной тысячи от правильного значения.
Дженнингер
25

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

Джо
источник
6

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

FlySwat
источник
3
Точность является ЕДИНСТВЕННЫМ преимуществом десятичных дробей, это правильно. Вы не должны спрашивать, когда вы должны использовать числа с плавающей запятой над десятичными. Это должно быть вашей первой мыслью. Тогда возникает вопрос, когда вы должны использовать десятичные дроби (и ответ здесь, когда точность имеет значение).
Охотник за экземплярами
3
@Daniel Straight, это смешно, но у меня противоположное мнение. Я думаю, что использование менее точного типа из-за его рабочих характеристик составляет предоптимизацию. Возможно, вам придется многократно платить за эту предоптимизацию, прежде чем вы поймете ее пользу.
Майкл Медоус
3
@ Майкл Мидоуз, я могу понять этот аргумент. Однако следует отметить, что одной из основных претензий к преждевременной оптимизации является то, что программисты не склонны знать, что будет медленным. Мы, без сомнения, знаем, что десятичные дроби медленнее, чем двойные. Тем не менее, я полагаю, что в большинстве случаев улучшение производительности не будет заметно пользователю. Конечно, в большинстве случаев точность тоже не нужна. Хех.
Экземпляр Охотник
Десятичная с плавающей точкой на самом деле МЕНЬШЕ точнее, чем двоичная с плавающей точкой, использующая такое же количество битов. Преимущество десятичной дроби заключается в том, что она может точно представлять дробные дроби, такие как 0,01, которые являются общими в финансовых расчетах.
Ден04
Ну, это не совсем правильно :) - во многих играх числа с плавающей запятой могут быть нежелательны, потому что они не согласованы. Смотрите здесь
BlueRaja - Дэнни Пфлугхофт
4

В некоторых учетах рассмотрите возможность использования целочисленных типов вместо или в сочетании. Например, допустим, что правила, по которым вы работаете, требуют, чтобы каждый результат вычисления переносился как минимум с 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 приводило к ошибкам! Целое число только по математике устранило проблему. Так что мой нетрадиционный ответ, по возможности, воздерживаться от двойных и десятичных.

G DeMasters
источник
3

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

Уилл Дин
источник
2

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

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

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

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

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

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

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

plugwash
источник
2

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

Поскольку float и double являются двоичными типами данных, у вас есть некоторые сложности и ошибки в числах округлений, поэтому, например, double будет округлять от 0,1 до 0,100000001490116, double также будет округлять от 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 (double, double). Десятичное число не поддерживается.

.NET Framework 4

[SecuritySafeCritical]
public static extern double Pow(double x, double y);
Джесон Мартаджая
источник
0

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

Крис Классен
источник