Я понимаю принцип этой проблемы, но это дает мне головную боль, чтобы думать, что это происходит во всем моем приложении, и мне нужно найти решение.
double Value = 141.1;
double Discount = 25.0;
double disc = Value * Discount / 100; // disc = 35.275
Value -= disc; // Value = 105.824999999999999
Value = Functions.Round(Value, 2); // Value = 105.82
Я использую двойники для представления довольно маленьких чисел. Как-то в расчете 141.1 - 35.275 двоичное представление результата дает число, которое составляет всего 0,0000000000001. К сожалению, поскольку я округляю это число, это дает неправильный ответ.
Я читал об использовании Decimals вместо Doubles, но я не могу заменить каждый экземпляр Double с десятичным. Есть ли более простой способ обойти это?
Если вы ищете точные представления значений, которые естественно десятичны, вам нужно будет заменить double
на decimal
всюду. Вы просто используете неправильный тип данных. Если вы использовали short
всюду для целых чисел, а затем выяснили, что вам нужно справляться с большими значениями, чем поддерживает, что бы вы сделали? Это та же самая сделка.
Однако, вы действительно должны попытаться понять, что происходит, начиная с... почему Value не равно точно 141.1, например.
У меня есть две статьи по этому поводу:
Вы должны использовать decimal
- для чего он нужен.
Поведение арифметики с плавающей запятой? Это то, что он делает. Он имеет ограниченную конечную точность. Не все числа точно представлены. На самом деле существует бесконечное число вещественных чисел, и только конечное число может быть представимо. Ключ к decimal
для этого приложения состоит в том, что он использует представление базы 10 - double
использует базу 2.
Проведя большую часть утра, пытаясь заменить каждый экземпляр "двойного" на "десятичный" и осознав, что я сражаюсь с проигравшей битвой, я снова посмотрел на свою функцию "Круг". Это может быть полезно тем, кто не может реализовать правильное решение:
public static double Round(double dbl, int decimals) {
return (double)Math.Round((decimal)dbl, decimals, MidpointRounding.AwayFromZero);
}
При первом присвоении значения десятичному значению, а затем вызове Math.Round, это вернет "правильное" значение.
Вместо того, чтобы использовать Round для округления числа, вы можете использовать некоторую функцию, которую вы пишете сами, которая использует небольшой epsilon при округлении, чтобы допускать ошибку. Это ответ, который вы хотите.
Ответ, который вы не хотите, но я все равно дам, это то, что если вам нужна точность, и поскольку вы имеете дело с деньгами, судя по вашему примеру, вы, вероятно, это делаете, вы не должны использовать двоичный плавающий математическая математика. Двоичная плавающая точка по своей сути неточна, и некоторые цифры просто не могут быть представлены правильно. Использование Decimal, которое делает base-10 с плавающей запятой, было бы намного лучшим подходом повсюду и позволит избежать дорогостоящих ошибок с вашими удвоениями.
decimal
является типом с плавающей запятой. И все же некоторые числа не могут быть представлены точно ... но все десятичные числа (в пределах соответствующего диапазона и точности) могут быть представлены точно.