2013-05-18 2 views
6

Math.Round(8.075, 2, MidpointRounding.AwayFromZero) возвращает 8.07, хотя он должен вернуть 8.08. Таинственно достаточно, 7.075 работает нормально, но 9.075 также возвращает 9.07!Ошибка в Math.round - что делать?

Что делать? Кто-нибудь знает метод округления без таких ошибок?

+2

Я очень сомнительно, .net бы такие ошибки, если это то, что это произойдет, то я уверен, что он был реализован таким образом по причине. –

+2

Вы можете использовать десятичный тип, поскольку они более точны: 'Math.Round (8.075m, 2, MidpointRounding.AwayFromZero) ' –

+0

нам нужен Джон Скит здесь – VladL

ответ

4

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

7.075 ==> 7.07500000000000017763568394002504646778106689453125 
8.075 ==> 8.074999999999999289457264239899814128875732421875 
9.075 ==> 9.074999999999999289457264239899814128875732421875 

Подробнее о точности с плавающей точкой: What Every Computer Scientist Should Know About Floating-Point Arithmetic.

+0

Как вы нашли настоящие ценности? –

+0

@AshBurlaczenko С java-программой, но я полагаю, вы можете сделать то же самое в .NET. – assylias

+0

@AshBurlaczenko: У меня есть программа C# для этого здесь: http://blogs.msdn.com/b/ericlippert/archive/2011/02/17/looking-inside-a-double.aspx и недавние последующие действия Сообщение здесь: http://ericlippert.com/2013/05/13/spot-the-defect-rounding/ –

9

Если посчитать с 10 пальцами, как это делают люди, у вас нет каких-либо проблем, выражающую десятичное значение 8.075 точно:

8.075 = 8 x 10^1 + 0 x 10^0 + 7 x 10^-1 + 5 x 10^-2 

Но подсчитывать компьютеры с 2-мя пальцами, они должны выразить это значение в полномочия 2:

8.075 = 1 x 2^3 + 0 x 2^2 + 0 x 2^1 + 0 x 2^0 + 0 x 2^-1 + 0 x 2^-2 + 0 x 2^-3 + 
      1 x 2^-4 + 0 x 2^-5 + 0 x 2^-6 + 1 x 2^-7 + 1 x 2^-8 + 0 x 2^-9 + 0 x 2^-10 + 
      1 x 2^-11 + ... 

я сдался с пальцем судороги введя термины, но дело в том, что независимо от того, сколько силы 2 вы добавляете, вы будете никогда получить именно 8.075m. Аналогичная проблема с тем, как люди никогда не могут написать результат 10/3 точно, она имеет бесконечное количество цифр во фракции. Вы можете точно записать результат этого выражения, когда вы считаете 6 пальцев.

У процессора, конечно, недостаточно памяти для хранения бесконечного количества бит для представления значения. Поэтому они должны усечь последовательность цифр, значение типа double может хранить 53 бит.

В результате десятичное значение 8.075 округляется, когда оно хранится в процессоре. Последовательность из 53 бит, преобразованная обратно в десятичную, равна ~ 8.074999999999999289. Который, как и ожидалось, округляется до 8.07 по вашему коду.

Если вы хотите получить результаты 10-ти пальцев, вам понадобится использовать тип данных, который хранит числа в базе 10. Это системный тип. NET. Fix:

decimal result = Math.Round(8.075m, 2, MidpointRounding.AwayFromZero) 

Обратите внимание на использование буквы м в 8.075m буквального во фрагменте, литерал типа десятичной. Который выбирает перегрузку Math.Round(), которая считается с 10 пальцами, ранее вы использовали перегрузку, использующую System.Double, версию с двумя пальцами.

Обратите внимание, что существует значительный недостаток для расчета с помощью System.Decimal, это slow. Многое, намного медленнее, чем вычисление с помощью System.Double, тип значения, который напрямую поддерживается процессором. Десятичная математика выполняется в программном обеспечении и не аппаратно ускоряется.

+1

Вот почему * настоящие * математики [счет с использованием 60 пальцев] (http://en.wikipedia.org/wiki/шестидесятеричной). – svick

-2

Возможное решение может быть:

(double)Math.Round((decimal)8.075, 2, MidpointRounding.AwayFromZero); 
Смежные вопросы