Закръгляването в Delphi
11 отговора
729 прегледа
Закръгляването в Delphi
Мисля, че доста отдавна го дискутирахме това, но наскоро закръгляването в Delphi отново събуди моето любопитство.
Става въпрос за стандартната функция Round. И по-точно, че Round(1.5) = 2 и Round(2.5) = 2 също! Четните и нечетните числа се закръгляват по различен начин. Не е проблем това да се "поправи" с друга функция, но в момента не мога да стопля защо е реализирано така. Идеи?
Mixy
Става въпрос за стандартната функция Round. И по-точно, че Round(1.5) = 2 и Round(2.5) = 2 също! Четните и нечетните числа се закръгляват по различен начин. Не е проблем това да се "поправи" с друга функция, но в момента не мога да стопля защо е реализирано така. Идеи?
Mixy
Re: Закръгляването в Delphi
The rounding uses Bankers rules, where an exact half value causes a rounding to an even number
Re: Закръгляването в Delphi
И за да не се мъчиш:
function RoundCorrect(R: Real): LongInt;
begin
Result:= Trunc(R);
if Frac(R) >= 0.5 then
Result:= Result + 1;
end;

function RoundCorrect(R: Real): LongInt;
begin
Result:= Trunc(R);
if Frac(R) >= 0.5 then
Result:= Result + 1;
end;
Re: Закръгляването в Delphi
Мерси, че ми спести мъките, относно "правилния" алгоритъм.
Но по-интересни са ми именно тези банкови правила и какво ги налага?
Mixy
Но по-интересни са ми именно тези банкови правила и какво ги налага?
Mixy
Re: Закръгляването в Delphi
Някъде на delphikingdom имаше статия по въпроса, но общо взето логиката е следната:
Закръгляването при *5 до по горното число е несиметрично, което при сумиране на много числа дава систематично отклонение нагоре, което не е верно при симетричното закръгляване до по-близкото четно.
Напр. нека да разгледаме числата:
0,1 0 0
0,2 0 0
0,3 0 0
0,4 0 0
0,5 1 0
0,6 1 1
0,7 1 1
0,8 1 1
0,9 1 1
1,0 1 1
1,1 1 1
1,2 1 1
1,3 1 1
1,4 1 1
1,5 2 2
1,6 2 2
1,7 2 2
1,8 2 2
1,9 2 2
2,0 2 2
==========
21 22 21
Закръгляването при *5 до по горното число е несиметрично, което при сумиране на много числа дава систематично отклонение нагоре, което не е верно при симетричното закръгляване до по-близкото четно.
Напр. нека да разгледаме числата:
0,1 0 0
0,2 0 0
0,3 0 0
0,4 0 0
0,5 1 0
0,6 1 1
0,7 1 1
0,8 1 1
0,9 1 1
1,0 1 1
1,1 1 1
1,2 1 1
1,3 1 1
1,4 1 1
1,5 2 2
1,6 2 2
1,7 2 2
1,8 2 2
1,9 2 2
2,0 2 2
==========
21 22 21
Re: Закръгляването в Delphi
NDeu е дал добър отговор (не го знаех). Мерси NDeu.
Наистина и аз съм си задавал този въпрос. Спрях да си го задавам когато почнах да ползвам:
SimpleRoundTo
Дефинирана е в unita Math в Delphi 7.
Наистина и аз съм си задавал този въпрос. Спрях да си го задавам когато почнах да ползвам:
SimpleRoundTo
Дефинирана е в unita Math в Delphi 7.
Re: Закръгляването в Delphi
За този род сметки използвам Currency вместо double с ф-ята FormatFloat().
Пиша на Буилдера, но се надявам, че и в Делфито има съответните аналози.
Пиша на Буилдера, но се надявам, че и в Делфито има съответните аналози.
Re: Закръгляването в Delphi
ИвКо, твоята функция не работи правилно за отрицателни числа.
Re: Закръгляването в Delphi
Мдааааааа.... Така е като пиша направо в интернет екплорера...
Извинявайте... друг път ще трябва да си отварям Делфито
Извинявайте... друг път ще трябва да си отварям Делфито
Re: Закръгляването в Delphi
OK, мерси на всички!
Това за натрупването при сумиране на закръглени числа не ми беше хрумвало.

Mixy
Това за натрупването при сумиране на закръглени числа не ми беше хрумвало.
Mixy
Re: Закръгляването в Delphi
мда хубава идейка, но ако от базата данни ти идва стойност NULL полето я прави 0.00 лв примерно (тоест на поле без стойност слага стойност - макар и нула)
или бъркам?
или бъркам?
Re: Закръгляването в Delphi
Винаги преди присвояването към съответният тип можеш да провериш предварително с IsNull() това условие.
Напоследък и аз отново почоплих този въпрос и ето до какви резултати стигнах със закръглянията на double в стринг:
- FormatFloat() - банкерско закръгляне т.е. грешно
- sprintf() на класа AnsiSting -банкерско закръгляне т.е. грешно
- sprintf() от стандартната С библиотека - банкерско закръгляне т.е. грешно
- snprintf() от стандартната С библиотека - банкерско закръгляне т.е. грешно
- wsprintf() - липсва %f типов спецификатор. Компилира се, но гърми при
изпълнение с ВСВ-то
- RoundTo() - вярно конвертиране
- SimpleRoundTo() - вярно конвертиране
- gcvt(),ecvt() и тем подобни отпадат предварително като морално остарели
А ето и самият пример
На празна форма се добавя RichEdit и бутон. При натискането на бутона се изпълнява:
int i5,i4;
double d;
String s;
//char szBuf[10]; // за C style sprintf()
i4 = i5 = 0;
RichEdit1->Lines->Clear();
for (int i = 0; i < 1000; i++)
{ d = double(i + 0.45);
s.sprintf("%5.1f",d); // смени sprintf() с другите ф-ии
RichEdit1->Lines->Add(s);
if (s[s.Length()] == '5')
i5++;
else
i4++;
}
RichEdit1->Lines->Add("Round to 4 = " + String(i4));
RichEdit1->Lines->Add("Round to 5 = " + String(i5));
1-те 10 реда в RichEdit-a
0.5
1.4
2.5
3.5
4.5
5.5
6.5
7.5
8.4
9.4
Интересното е ,че с Currency вместо double горният пример работи нормално.
sprintf() на стандартната С библиотека оттпада и поради друго съображение - възможно препълване на буфера. snsprintf() е по добрият вариант в това отношение.
Напоследък и аз отново почоплих този въпрос и ето до какви резултати стигнах със закръглянията на double в стринг:
- FormatFloat() - банкерско закръгляне т.е. грешно
- sprintf() на класа AnsiSting -банкерско закръгляне т.е. грешно
- sprintf() от стандартната С библиотека - банкерско закръгляне т.е. грешно
- snprintf() от стандартната С библиотека - банкерско закръгляне т.е. грешно
- wsprintf() - липсва %f типов спецификатор. Компилира се, но гърми при
изпълнение с ВСВ-то
- RoundTo() - вярно конвертиране
- SimpleRoundTo() - вярно конвертиране
- gcvt(),ecvt() и тем подобни отпадат предварително като морално остарели
А ето и самият пример
На празна форма се добавя RichEdit и бутон. При натискането на бутона се изпълнява:
int i5,i4;
double d;
String s;
//char szBuf[10]; // за C style sprintf()
i4 = i5 = 0;
RichEdit1->Lines->Clear();
for (int i = 0; i < 1000; i++)
{ d = double(i + 0.45);
s.sprintf("%5.1f",d); // смени sprintf() с другите ф-ии
RichEdit1->Lines->Add(s);
if (s[s.Length()] == '5')
i5++;
else
i4++;
}
RichEdit1->Lines->Add("Round to 4 = " + String(i4));
RichEdit1->Lines->Add("Round to 5 = " + String(i5));
1-те 10 реда в RichEdit-a
0.5
1.4
2.5
3.5
4.5
5.5
6.5
7.5
8.4
9.4
Интересното е ,че с Currency вместо double горният пример работи нормално.
sprintf() на стандартната С библиотека оттпада и поради друго съображение - възможно препълване на буфера. snsprintf() е по добрият вариант в това отношение.