Является ли двойное умножение разбитым в .NET?

Если я выполняю следующее выражение в С#:

double i = 10*0.69;

i: 6.8999999999999995. Зачем?

Я понимаю, что числа, такие как 1/3, могут быть трудно представлять в двоичном формате, поскольку у него бесконечные повторяющиеся десятичные разряды, но это не относится к 0,69. И 0,69 легко может быть представлено в двоичном, одном двоичном числе для 69, а другое - в позиции десятичного разряда.

Как мне обойти это? Используйте тип decimal?

Ответ 1

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

Фактически, ваш код на самом деле не выполняет арифметику во время выполнения в этом конкретном случае - компилятор выполнит ее, а затем сохранит константу в сгенерированном исполняемом файле. Тем не менее, он не может хранить точное значение 6.9, потому что это значение не может быть точно представлено в формате с плавающей точкой, так же как 1/3 не может быть точно сохранено в конечном десятичном представлении.

Посмотрите, поможет ли эта статья.

Ответ 2

почему структура не работает вокруг этого и не скрывает эту проблему от меня и не дает мне правильный ответ, 0,69!!!

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

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

Так будет каждый другой метод хранения чисел, так как у нас нет бесконечных бит. Наша работа в качестве программистов - работать с ограниченными ресурсами, чтобы сделать крутые вещи. Они получили от вас 90% пути, просто получите факел домой.

Ответ 3

И 0,69 легко представить в двоичное, одно двоичное число для 69 и другой - для обозначения положения десятичное место.

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

Итак, вы думаете, что для этого двойника есть две части с целым числом: 69 и делить на 100, чтобы получить десятичное место для перемещения - что могло бы также можно выразить так: 69 x 10 на мощность -2.

Однако поплавки сохраняют "положение точки" в качестве базы-2.

Ваше поплавок фактически хранится как:
   68999999999999995 x 2 на мощность некоторого большого отрицательного числа

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

Ответ 4

но почему структура не работает вокруг этого и скрывает эту проблему от меня и дает правильный ответ, 0.69!!!

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

Более эффективное решение состоит в том, чтобы не выводить полное значение представления и явно указывать точность, необходимую для вашего вывода. Если вы отформатируете вывод до двух знаков после запятой, вы увидите ожидаемый результат. Однако, если это финансовое приложение десятичное - это именно то, что вы должны использовать - вы видели Superman III (и Office Space), не так ли?)

Заметим, что все они являются конечной аппроксимацией бесконечного диапазона, а просто десятичной и двойной используют другой набор приближений. Преимущество десятичного числа - это то же самое приближение, которое вы делали бы, если бы выполняли расчет самостоятельно. Например, если вы рассчитали 1/3, вы в конечном итоге прекратите писать 3, когда это будет "достаточно хорошо".

Ответ 5

По той же причине, что 1/3 в десятичных системах получается как 0.333333333333333333333333333, а не точная доля, которая бесконечно длинная.

Ответ 6

Чтобы обойти это (например, отобразить на экране), попробуйте следующее:

double i = (double) Decimal.Multiply(10, (Decimal) 0.69);

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