Математика C # дает неправильные результаты!

2

Я понимаю принцип этой проблемы, но это дает мне головную боль, чтобы думать, что это происходит во всем моем приложении, и мне нужно найти решение.

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 с десятичным. Есть ли более простой способ обойти это?

  • 1
    Округлить до 3 десятичных знаков, а затем округлить до 2 десятичных? Ах ... и имейте в виду, что Math.Round округляется с использованием округления Banker's, так что 0,5 округляется вдвое больше, а наполовину меньше. Есть возможность использовать «стандартное» округление. Это MidpointRounding.AwayFromZero.
  • 1
    Поскольку мы говорим о валютных данных здесь, я думаю, что у вас должен быть этот рефакторинг в ближайшее время.
Теги:
floating-point
math
division

4 ответа

15
Лучший ответ

Если вы ищете точные представления значений, которые естественно десятичны, вам нужно будет заменить double на decimal всюду. Вы просто используете неправильный тип данных. Если вы использовали short всюду для целых чисел, а затем выяснили, что вам нужно справляться с большими значениями, чем поддерживает, что бы вы сделали? Это та же самая сделка.

Однако, вы действительно должны попытаться понять, что происходит, начиная с... почему Value не равно точно 141.1, например.

У меня есть две статьи по этому поводу:

  • 1
    +1 За то, что у тебя есть какая-то рукописная книга, на всякий случай.
3

Вы должны использовать decimal - для чего он нужен.

Поведение арифметики с плавающей запятой? Это то, что он делает. Он имеет ограниченную конечную точность. Не все числа точно представлены. На самом деле существует бесконечное число вещественных чисел, и только конечное число может быть представимо. Ключ к decimal для этого приложения состоит в том, что он использует представление базы 10 - double использует базу 2.

0

Проведя большую часть утра, пытаясь заменить каждый экземпляр "двойного" на "десятичный" и осознав, что я сражаюсь с проигравшей битвой, я снова посмотрел на свою функцию "Круг". Это может быть полезно тем, кто не может реализовать правильное решение:

public static double Round(double dbl, int decimals) {            
        return (double)Math.Round((decimal)dbl, decimals, MidpointRounding.AwayFromZero);
    }

При первом присвоении значения десятичному значению, а затем вызове Math.Round, это вернет "правильное" значение.

0

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

Ответ, который вы не хотите, но я все равно дам, это то, что если вам нужна точность, и поскольку вы имеете дело с деньгами, судя по вашему примеру, вы, вероятно, это делаете, вы не должны использовать двоичный плавающий математическая математика. Двоичная плавающая точка по своей сути неточна, и некоторые цифры просто не могут быть представлены правильно. Использование Decimal, которое делает base-10 с плавающей запятой, было бы намного лучшим подходом повсюду и позволит избежать дорогостоящих ошибок с вашими удвоениями.

  • 4
    Он не должен использовать двоичный тип с плавающей точкой. Обратите внимание, что decimal является типом с плавающей запятой. И все же некоторые числа не могут быть представлены точно ... но все десятичные числа (в пределах соответствующего диапазона и точности) могут быть представлены точно.
  • 0
    Спасибо за исправление. Отредактировано соответствующим образом.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню