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

StackOverflow https://stackoverflow.com/questions/803225

Вопрос

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

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

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

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

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

Это было полезно?

Решение

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

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

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

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

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

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

Другие советы

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

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

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

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

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

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

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

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

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

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

  • десятичная дробь = 28-29 значащих цифр
  • двойной = 15-16 значащих цифр
  • с плавающей запятой = 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

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

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

Итак, снова возьмем пример портфолио:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Графический голод?достаточно float или 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);

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top