Home » Создание сайтов и программирование » Почему Round "раундит" "не правильно" или все про округление в .NET

Почему Round "раундит" "не правильно" или все про округление в .NET

Как Вы думаете, какой результат получится в результате выполнения этого кода:

Если Вы думаете, что получится 4 и 5, то ошибаетесь. В обоих случаях результатом будет 4. Тот же самый результат Вы получите и в Visual Basic 6.0. В .NET статический метод Round() класса Math округляет половину к ближайшему четному. В школе же нас учили, что половина всегда округляется в большую сторону. Поэтому многие (и я в том числе) очень удивляются, узнав о таком “неправильном” округлении. Часто незнание этого факта может привести к неправильным расчетам, если алгоритмы преполагают “обычное” округление.

Что же это такое? Очередной баг Microsoft? Вовсе нет! Просто существует несколько способов округления.

Округление в меньшую сторону

Простейший случай – когда цифры после заданной точности просто отбрасываются (округляем до целого):

3.9 округляется до 3
-3.9 округляется до -3

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

С учетом знака -3.9 округляется до -4. Метод Floor() класса Math производит несимметричное округление:

Округление в большую сторону

Несимметричное округление до ближайшего большего или равного целого выполняет метод Ceiling() класса Math:

Заметьте, что Floor() и Ceiling() округляют всегда до целого.

Но чаще всего приходится округлять не в меньшую или большую сторону, а к ближайшему числу (с заданной точностью). В этом случае погрешность будет меньшей. Главный вопрос, который возникает в этом случае – как округлять половину?

Арифметическое округление

Это привычное нам округление, когда половина округляется в большую сторону:

3.5 округляется до 4
4.5 округляется до 5

Как и в предыдущих случаях, можно рассматривать симметричное и несимметричное арифметическое округление.

Банковское округление

Если складывать много чисел, округляя .5 всегда в большую сторону, то возникнет перекос, который будет тем больше, чем больше чисел мы складываем. Банковское округление позволяет минимизировать этот перекос. В этом случае половина округляется к ближайшему четному. Метод Round() класса Math реализует именно банковское округление. В качестве параметра он принимает округляемое значение и, возможно, точность, до которой необходимо выполнить округление. Если точность не указана, то округление выполняется до целого.

Случайное округление

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

Попеременное (alternate) округление

В этом случае .5 округляется попеременно то в большую сторону, то в меньшую.

Что же делать, если надо произвести арифметическое округление? К сожалению, Microsoft не реализовала соответствующий метод в классе Math (так же, как и для случайного и попеременного округлений). Но выход, конечно, есть.

Во первых, это класс SqlDecimal из пространства имен System.Data.SqlTypes со статическим методом Round:

Это отличие связано с тем, что Round из Sql Server выполняет математическое округление.

Во вторых, можно самостоятельно написать метод для математического (и не только) округления. Например, вот реализация на C# для симметричного арифметического округления.

Я предпочитаю поместить несколько перегрузок приведенной реализации (для float, double, decimal) в некоторый служебный класс и использовать их вместо Math.Round() (мне пока не приходилось реализовывать задачи, требующие банковского округления).

Реализацию описанных алгоритмов округления на Visual Basic 6.0 можно найти здесь: http://support.microsoft.com/default.aspx?scid=kb;en-us;196652

Добавлено 25.10.2005

В .NET 2.0 метод Round() класса Math имеет перегрузки с параметром mode типа MidpointRounding, определяющим, как будет округляться половина. MidpointRounding может принимать два значения:

  • AwayFromZero – половина округляется к ближайшему числу, которое дальше от нуля (т.е обыкновенное математическое симметричное округление).
  • ToEven – округление половины к ближайшему четному – единственная текущая реализация Round(). Естественно, если параметр mode не задан, по умолчанию используется ToEven (для совместимости).

buy abilify buy accutane buy aceon buy aciphex buy actos buy adalat buy aggrenox buy albenza buy aldactone buy alesse buy allegra buy alprostadil buy altace buy amalaki buy amaryl buy amoxil buy anafranil buy antabuse buy arava buy arcoxia buy aricept buy arjuna buy artane buy asacol buy astelin buy atarax buy augmentin buy avalide buy avapro buy avelox buy avodart buy ayurslim buy azulfidine buy bactrim buy bactroban buy benicar buy betagan buy betnovate buy biaxin buy bimatoprost buy botox buy brahmi buy buspar buy bystolic buy calan buy cardura buy casodex buy cefixime buy ceftin buy celebrex buy celexa buy cenforce buy chloromycetin buy cialis buy cipro buy clarinex buy claritin buy cleocin buy clomid buy combivir buy copegus buy cordarone buy coreg buy coumadin buy cozaar buy crestor buy cymbalta buy cytoxan buy deltasone buy depakote buy desyrel buy detrol buy diabecon buy diamox buy diflucan buy diovan buy duphaston buy duricef buy effexor buy elavil buy eldepryl buy epivir buy eulexin buy evecare buy evista buy exelon buy famvir buy feldene buy flagyl buy fliban buy flomax buy flonase buy floxin buy geodon buy glucophage buy glucotrol buy glucovance buy grifulvin buy guduchi buy haridra buy himplasia buy hytrin buy hyzaar buy ilosone buy imdur buy imitrex buy imodium buy inderal buy indocin buy janya buy kapikachhu buy karela buy keflex buy kemadrin buy lamictal buy lanoxin buy lasix buy lasuna buy levaquin buy levitra buy lexapro buy lioresal buy lipitor buy lopid buy lopressor buy lozol buy luvox buy manjishtha buy megaslim buy menosan buy meshashringi buy mestinon buy metaglip buy micardis buy micronase buy microzide buy minipress buy mobic buy moduretic buy motilium buy motrin buy myambutol buy mysoline buy naprosyn buy neem buy neurontin buy nexium buy nizoral buy nolvadex buy noroxin buy norvasc buy omnicef buy oxytrol buy pamelor buy paracetamol buy parlodel buy paxil buy penisole buy periactin buy persantine buy plavix buy ponstel buy prandin buy precose buy prednisone buy prevacid buy priligy buy prilosec buy prograf buy propecia buy proscar buy protonix buy provestra buy punarnava buy rebetol buy reglan buy requip buy retrovir buy risperdal buy rulide buy serevent buy seroquel buy shallaki buy shatavari buy shigru buy sinemet buy sinequan buy singulair buy speman buy sporanox buy starlix buy strattera buy stromectol buy sumycin buy suprax buy sustiva buy symmetrel buy tegretol buy tenoretic buy terramycin buy tofranil buy topamax buy trandate buy trental buy tricor buy trikatu buy trileptal buy triphala buy tulasi buy urispas buy uroxatral buy valtrex buy vantin buy vasaka buy vasotec buy ventolin buy vermox buy viagra buy vigorelle buy vigrx buy voltaren buy vrikshamla buy vytorin buy wondersleep buy xenical buy zanaflex buy zantac buy zebeta buy zestoretic buy zetia buy ziac buy zithromax buy zocor buy zofran buy zoloft buy zovirax buy zyloprim buy zyrtec viagra

buy abilify buy accutane buy aceon buy aciphex buy actos buy adalat buy aggrenox buy albenza buy aldactone buy alesse buy allegra buy alprostadil buy altace buy amalaki buy amaryl buy amoxil buy anafranil buy antabuse buy arava buy arcoxia buy aricept buy arjuna buy artane buy asacol buy astelin buy atarax buy augmentin buy avalide buy avapro buy avelox buy avodart buy ayurslim buy azulfidine buy bactrim buy bactroban buy benicar buy betagan buy betnovate buy biaxin buy bimatoprost buy botox buy brahmi buy buspar buy bystolic buy calan buy cardura buy casodex buy cefixime buy ceftin buy celebrex buy celexa buy cenforce buy chloromycetin buy cialis buy cipro buy clarinex buy claritin buy cleocin buy clomid buy combivir buy copegus buy cordarone buy coreg buy coumadin buy cozaar buy crestor buy cymbalta buy cytoxan buy deltasone buy depakote buy desyrel buy detrol buy diabecon buy diamox buy diflucan buy diovan buy duphaston buy duricef buy effexor buy elavil buy eldepryl buy epivir buy eulexin buy evecare buy evista buy exelon buy famvir buy feldene buy flagyl buy fliban buy flomax buy flonase buy floxin buy geodon buy glucophage buy glucotrol buy glucovance buy grifulvin buy guduchi buy haridra buy himplasia buy hytrin buy hyzaar buy ilosone buy imdur buy imitrex buy imodium buy inderal buy indocin buy janya buy kapikachhu buy karela buy keflex buy kemadrin buy lamictal buy lanoxin buy lasix buy lasuna buy levaquin buy levitra buy lexapro buy lioresal buy lipitor buy lopid buy lopressor buy lozol buy luvox buy manjishtha buy megaslim buy menosan buy meshashringi buy mestinon buy metaglip buy micardis buy micronase buy microzide buy minipress buy mobic buy moduretic buy motilium buy motrin buy myambutol buy mysoline buy naprosyn buy neem buy neurontin buy nexium buy nizoral buy nolvadex buy noroxin buy norvasc buy omnicef buy oxytrol buy pamelor buy paracetamol buy parlodel buy paxil buy penisole buy periactin buy persantine buy plavix buy ponstel buy prandin buy precose buy prednisone buy prevacid buy priligy buy prilosec buy prograf buy propecia buy proscar buy protonix buy provestra buy punarnava buy rebetol buy reglan buy requip buy retrovir buy risperdal buy rulide buy serevent buy seroquel buy shallaki buy shatavari buy shigru buy sinemet buy sinequan buy singulair buy speman buy sporanox buy starlix buy strattera buy stromectol buy sumycin buy suprax buy sustiva buy symmetrel buy tegretol buy tenoretic buy terramycin buy tofranil buy topamax buy trandate buy trental buy tricor buy trikatu buy trileptal buy triphala buy tulasi buy urispas buy uroxatral buy valtrex buy vantin buy vasaka buy vasotec buy ventolin buy vermox buy viagra buy vigorelle buy vigrx buy voltaren buy vrikshamla buy vytorin buy wondersleep buy xenical buy zanaflex buy zantac buy zebeta buy zestoretic buy zetia buy ziac buy zithromax buy zocor buy zofran buy zoloft buy zovirax buy zyloprim buy zyrtec viagra

  • Rometsss

    Пора мелкософту гнать своих индусов с их “восхитительной” логикой и миропредставлением :)
    А потом у них винда и прочие приложения глючат почемуто…
    Обычная арифметика: 30-20=10, как у десяти может быть середина? там с одной стороны 0-4 (5 цифр) и с другой 5-9 (5 цифр)!

    • А при чем здесь индусы? Просто есть разные способы округления, о которых я рассказал в статье, и в первой версии .NET использовался не тот, к котрому привыкли мы. В этом нет ничего страшного, если знать.

      Ноль после запятой не учитывается, так что цифр, которые надо округлять, остается 9. И цифра 5 – середина.

  • Dasistgut

    А если ещё немножко подумать, то округлять надо результат, а не слагаемые.

    • А если еще подумать, то можно понять, что слагаемые, например, могут уже храниться в базе. В любом случае, результат можно округлять по разному (о чем и идет речь в статье).

  • Venom

    > простым привидением 

  • tarenych

    есть еще волшебные округления чисел в/за пределами 32768
    пример
    32767.075 32767.08
    32768.075 32768.07

    так округляет и Math.Round(32768.075, 2, MidpointRounding.AwayFromZero) и public static double Round(32768.075, 2)

    • tarenych

      это связано с хранением чисел с плавающей точкой, поэтому лучше использовать Decimal.Round(32768.075, MidpointRounding.AwayFromZero) = 32767.08