Неужели дубль за деньги не годится?

Я всегда говорю в C #, что переменная типа double не годится за деньги. Могло случиться все странное. Но я не могу создать пример, чтобы продемонстрировать некоторые из этих проблем. Кто-нибудь может привести такой пример?

(edit; этот пост изначально был помечен как C #; некоторые ответы относятся к конкретным деталям decimal, что, следовательно, означает _ 2_).

(редактировать 2: я конкретно просил некоторый код С #, поэтому я не думаю, что это только язык-агностик)


person doekman    schedule 25.11.2008    source источник


Ответы (8)


Очень и очень неподходящий. Используйте десятичную дробь.

double x = 3.65, y = 0.05, z = 3.7;
Console.WriteLine((x + y) == z); // false

(пример со страницы Джона здесь - рекомендуется прочитать ;-p)

person Marc Gravell    schedule 25.11.2008
comment
Черт возьми, если бы я знал, что у меня есть пример на моей странице, я бы не придумал другой;) - person Jon Skeet; 25.11.2008

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

Вот конкретный пример:

using System;

class Test
{
    static void Main()
    {
        double x = 0.1;
        double y = x + x + x;
        Console.WriteLine(y == 0.3); // Prints False
    }
}
person Jon Skeet    schedule 25.11.2008
comment
Если вы используете службу, которая возвращает значения в двойной валюте, которые вы не можете контролировать, есть ли какие-то подводные камни, о которых следует подумать при преобразовании их в десятичные числа? Потеря точности и т. Д. - person vikingben; 13.10.2016
comment
@vikingben: Абсолютно - по сути, это сломанный способ делать что-то, и вам нужно решить, как лучше всего интерпретировать данные. - person Jon Skeet; 13.10.2016

Да это непригодно.

Если я правильно помню, double имеет около 17 значащих чисел, поэтому обычно ошибки округления происходят далеко за десятичной точкой. Большинство финансовых программ использует 4 десятичных знака после десятичной точки, что оставляет 13 десятичных знаков для работы, поэтому максимальное число, с которым вы можете работать для отдельных операций, по-прежнему намного выше, чем государственный долг США. Но ошибки округления со временем будут накапливаться. Если ваше программное обеспечение работает долгое время, вы в конечном итоге начнете терять центы. Некоторые операции только усугубят ситуацию. Например, добавление большого количества к маленькому вызовет значительную потерю точности.

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

изменить
Согласно этому сайту http://msdn.microsoft.com/en-us/library/678hzkk9.aspx В действительности двойные числа содержат от 15 до 16 значащих цифр вместо 17.

@Jon Skeet decimal больше подходит, чем double, из-за его более высокой точности, 28 или 29 значащих десятичных знаков. Это означает меньшую вероятность того, что накопленные ошибки округления станут значительными. Типы данных с фиксированной точкой (т. Е. Целые числа, представляющие центы или сотые доли цента, как я видел, использованные), такие как упоминания Boojum, на самом деле подходят лучше.

person Mendelt    schedule 25.11.2008
comment
Обратите внимание, что System.Decimal, предлагаемый тип для использования в .NET, по-прежнему является типом с плавающей запятой, но это десятичная точка с плавающей запятой, а не двоичная с плавающей запятой. Я подозреваю, что в большинстве случаев это важнее, чем фиксированная точность. - person Jon Skeet; 25.11.2008
comment
Именно в этом-то и проблема. Валюта в настоящее время обычно десятичная. Однако еще до того, как фондовые рынки США стали десятичными, использовались двоичные дроби (в какой-то момент я начал видеть 256 и даже 1024), и поэтому удвоение было бы более подходящим, чем десятичные дроби для цен на акции! Фунт стерлингов до десятичной системы был бы настоящей головной болью, хотя стоил 960 фартингов за фунт; он не является ни десятичным, ни двоичным, но, безусловно, предоставляет большое количество простых множителей для простых дробей. - person Jeffrey Hantin; 11.11.2010
comment
Даже более важно, чем просто десятичная точка с плавающей запятой, decimal выражение x + 1 != x всегда истинно. Кроме того, он сохраняет точность, поэтому вы можете отличить 1 от 1.0. - person Gabe; 16.03.2011
comment
@Gabe: Эти свойства имеют смысл только в том случае, если значения масштабируются так, чтобы значение 1 представляло наименьшую денежную единицу. Значение Decimal может потерять точность справа от десятичной точки без указания на проблему. - person supercat; 02.03.2013
comment
double имеет 15,9 значащих десятичных цифр, учитывая только целые значения. Ситуация после десятичной точки зависит от значения. - person user207421; 18.07.2018

Поскольку decimal использует коэффициент масштабирования, кратный 10, числа вроде 0,1 могут быть представлены точно. По сути, десятичный тип представляет это как 1/10 ^ 1, тогда как double будет представлять это как 104857/2 ^ 20 (на самом деле это будет больше похоже на действительно-большое-число / 2 ^ 1023).

decimal может точно представлять любое значение с основанием 10, содержащее до 28/29 значащих цифр (например, 0,1). double не может.

person Richard Poole    schedule 25.11.2008
comment
Десятичное число не имеет 96 значащих цифр. Он имеет 96 значащих битов. В десятичной дроби около 28 значащих цифр. - person Jon Skeet; 25.11.2008
comment
На каком языке вы говорите о десятичном виде? Или все языки, поддерживающие этот тип, поддерживают его точно так же? Могу захотеть уточнить. - person Adam Davis; 25.11.2008
comment
@Adam - в этом посте изначально был тег C #, поэтому мы говорим конкретно о System.Decimal. - person Marc Gravell; 25.11.2008
comment
Ой, хорошо замеченный Джон! Исправлено. Адам, я говорю о C # в соответствии с вопросом. Есть ли в других языках тип, называемый десятичным? - person Richard Poole; 25.11.2008
comment
@Richard: Ну, все языки, основанные на .NET, делают, поскольку System.Decimal не является уникальным типом C #, это тип .NET. - person awe; 12.01.2010
comment
@awe - я имел в виду языки, отличные от .NET. Я по незнанию не знаю ни одного, у которого есть собственный тип с плавающей запятой base 10, но я не сомневаюсь, что они существуют. - person Richard Poole; 19.01.2010

Насколько я понимаю, большинство финансовых систем выражают валюту с помощью целых чисел, т. Е. Считая все в центах.

На самом деле двойная точность IEEE может представлять все целые числа точно в диапазоне от -2 ^ 53 до + 2 ^ 53. (Hacker's Delight, стр. 262) Если вы используете только сложение, вычитание и умножение и сохраняете все целые числа в пределах этого диапазона, вы не увидите потери точности. Однако я бы очень осторожно относился к разделению или более сложным операциям.

person Boojum    schedule 25.11.2008
comment
Если вы собираетесь использовать только целые числа, почему бы не использовать для начала целочисленный тип? - person Jon Skeet; 25.11.2008
comment
Хех - int64_t может представлять все целые числа точно в диапазоне от -2 ^ 63 до + 2 ^ 63-1. Если вы используете только сложение, вычитание и умножение и сохраняете все целые числа в этом диапазоне, вы не увидите потери точности. Однако я бы очень осторожно относился к разделению. - person Steve Jessop; 25.11.2008
comment
Некоторые устаревшие системы, которые (увы?) Все еще используются, поддерживают double, но не поддерживают какие-либо 64-битные целочисленные типы. Я бы предположил, что выполнение вычислений как double с масштабированием, при котором любое семантически необходимое округление всегда будет до целых единиц, является наиболее эффективным подходом. - person supercat; 10.06.2012

Использование double, когда вы не знаете, что делаете, не подходит.

«дабл» может представлять собой сумму в триллион долларов с погрешностью в 1/90 цента. Так вы получите очень точные результаты. Хотите подсчитать, сколько стоит отправить человека на Марс и вернуть его живым? двойной подойдет.

Но в отношении денег часто существуют очень конкретные правила, согласно которым определенный расчет должен давать определенный результат, а не другой. Если вы подсчитываете сумму, которая очень-очень близка к 98,135 долларов, то часто будет правило, определяющее, должен ли результат быть 98,14 или 98,13 долларов, и вы должны следовать этому правилу и получить требуемый результат. .

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

person gnasher729    schedule 23.01.2015

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

person Thomas Hansen    schedule 25.11.2008
comment
Осторожный. Любое представление с плавающей запятой будет иметь ошибки округления, включая десятичные числа. Просто десятичная дробь будет округляться способами, которые интуитивно понятны людям (и обычно подходят для денег), а двоичная с плавающей запятой - нет. Но для нефинансового анализа чисел double часто намного лучше, чем decimal, даже в C #. - person Daniel Pryden; 31.08.2009

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

См. http://www.idinews.com/moneyRep.html.

Таким образом, фиксированная точка длинная. Любой из них потребляет 8 байтов, что, безусловно, предпочтительнее, чем 16, потребляемые десятичным элементом.

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

person Conrad Weisert    schedule 08.06.2015
comment
Если вы ссылаетесь на статью, которую вы написали, которая не согласуется с десятилетиями общепринятой практики и экспертных опций, что с плавающей запятой не подходят для представления финансовых транзакций, потребуется немного больше резервной копии, чем одна страница. - person MuertoExcobito; 08.06.2015