Pers.narod.ru. Алгоритмы. Round в C++

Как известно, в C/C++ нет стандартного метода округления вещественных чисел до целых, и одна из типовых для начинающих программистов задач - собственная реализация такого метода, обычно называемого round. Корректно и в общем виде решить эту задачу не так легко, хотя если речь идёт просто об округлении вещественного до целого - большой проблемы нет и вполне сработает такая реализация:

//round in c++
#include <stdio.h>

#include <math.h>

double round (double x) {
 return ((x - floor(x)) >= 0.5) ? ceil(x) : floor(x);
}

int main() {
 double f[]={-3.7,3.7,-0.15,0.15,0.5,0.65,0}; //0 must be the last item   
 for (int i=0; ;i++) {   
  printf ("\nround(%lf)=%lf",f[i],round(f[i]));   
  if (f[i]==0) break;
 }
 getchar();
 return 0;    
}

Гораздо чаще проблемы вызывает округление числа до N разрядов в дробной части, так, естественно развивающая предыдущий листинг реализация округления чисел до 2 знаков в дробной части:

#include <stdio.h>

#include <math.h>

double round2 (double x) {
 double x2=x*100;
 return (x2 - floor(x2)>=0.5 ? ceil(x2) : floor(x2))/100;
}

int main() {
 double f[]={1.255,123.255,1000.5,123.49,0}; //0 must be the last item
 //1.255 -> 1.25 - error!
 for (int i=0; ;i++) {
  printf ("\nround(%lf)=%.02lf",f[i],round2(f[i]));
  if (f[i]==0) break;
 }
 getchar();
 return 0;
}

работает некорректно (см. замечание в листинге).

В библиотеках VCL и CLX, реализующих типовые интерфейсные компоненты для GUI графических операционных систем, сохраняется та же проблема, приведём кусочек кода на C++ Builder:

void __fastcall TForm1::Button1Click(TObject *Sender) {
 char buffer[80];
 sprintf (buffer," round(%lf)=%.02lf",1.255,round2(1.255));
 Memo1->Lines->Add(AnsiString(buffer));
 SetRoundMode(rmNearest);
 Memo1->Lines->Add(RoundTo(1.255,-2));
}

Здесь округление до 2 знаков в дробной части делается с помощью нашего показанного выше метода round2 и стандартной функции RoundTo из библиотеки VCL. Обратите внимание, что в заголовочный файл модуля *.h должна быть подключена библиотека

#include <Math.hpp>

а не сишная <math.h>. По скриншоту окна приложения видно, что RoundTo тоже сработает неверно на "волшебном" числе 1.255:

ошибка округления

Между прочим, RoundTo считается так называмым "банкирским" округлением, а есть ещё SimpleRoundTo — обычное арифметическое округление, но оно на нашем числе работает также некорректно.

Можно попробовать в качестве альтернативы классическое

char string[20];
sprintf(string,"%.02f",var);

- после чего число содержится в Си-строке string. Увы, результат в моём C++ Builder при округлении до 2 знаков после запятой оказался столь же неверным - 1.25 вместо 1.26.

Остаётся либо использовать непростые и платформенно-зависимые решения, как здесь, либо искать Си-среду, где округление есть и сделано без ошибки, либо, наконец, использовать "шаманские" коды:

float round(float f) { 
 double t = (double)f + 6755399441055744.0; 
 return (float)*((int *)(&t)); 
}

Забавно, но это работает :) А вот версия для 2 разрядов после запятой, построенная на этой round,

float round2 (float f) { // до сотых
 float rf = round(f); 
 float t = (f - rf) * 100; 
 return rf + round(t) * 0.01f; 
}

не смогла правильно округлить упрямое число 1.255 в стареньком Borland C++ 3.1, но в C++ Builder 6 уже сработала верно. Значит ли это, что не найдётся другого "упрямого" числа? Думаю, вопрос остаётся открытым, поищите решение, интересно...

Рейтинг@Mail.ru

вверх гостевая; E-mail
Hosted by uCoz