Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: Общие вопросы > Фокусы Delphi


Автор: Poseidon 10.3.2005, 03:41
Значит так друзья. Пишу программу и столкнулся с "детской" процедуркой. Код немного переделанный (что бы понятно было), но суть ясна. Значит вот он:
Код
var
str: string;
x,y: real;
begin
x:= 0.5;
y:= 1.5;
str:= IntToStr( Round(x) + Round(y) );
Label1.Caption:= str ;
end;

Так вот сколько вы ожидаете увидеть в Label1? А ожидал 3. А там 2!
Вроде встречался с функцией Round и знаю, что она должна округлять по извесным правилам. Т.е. 0.5 округляется до 1, а 1.5 до 2. Но почему же тогда 1 + 2 = 2 ?
Может кто встречался с таким? Это хорошо, что у меня так получилось, что я сразу нашел. А ведь из-за этого погут все расчеты пойти на смарку. smile

Автор: RA 10.3.2005, 04:40
Цитата(Poseidon @ 10.3.2005, 03:41)
Т.е. 0.5 округляется до 1


0.5 округляется до 0.

Добавлено @ 04:45
попробуй вместо Round использовать Ceil, ибо чеил округляет число в сторону увеличения, а round до ближайшего целого.


Код


... модуль Math

//Округление в сторону увеличения
function Ceil(const X: Extended): Integer;
begin
  Result := Integer(Trunc(X));
  if Frac(X) > 0 then
    Inc(Result);
end;

//Округление в сторону уменьшения
function Floor(const X: Extended): Integer;
begin
  Result := Integer(Trunc(X));
  if Frac(X) < 0 then
    Dec(Result);
end;


Автор: SPrograMMer 10.3.2005, 17:48
Из того модуля Math:
Код

Function RoundTo(const AValue:Double;const ADigit:TRoundToRange):Double;

Попробуй сказать:
Код

...
str:= IntToStr( RoundTo(x,0) + RoundTo(y,0) );
...

Автор: Poseidon 11.3.2005, 04:05
RAdmin
Цитата
0.5 округляется до 0.
Это в этой процедуре. А если мы, к примеру, возьмем
Код
x:= 1.5;
y:= 2.5;

то получим не желанные 5, а 4!
Цитата
попробуй вместо Round использовать Ceil

Не подходит! Нужно именно округлять по правилам (< 5 в меншую, >=5 в большую сторону). Конечно можно устроить проверку символа после запятой и по нему определять, куда округлять в большую (Ceil) или меншую (Trunc) сторону. Но все же хотелось бы разобраться, почему так происходит? smile Ведь по правилам 0,5 округляется до 1. А Delphi округляет до 0. При этом 1,5 округляется до 2 и 2,5 до 2 !!! 3,5 до 4 и 4,5 до 4 !!! И так далее. Что это? Ошибка Borland? Или какая-то неизвестная тонкость функции Round?

В общем-то начиная эту тему, я не ставил себе целью найти помощь. В Delphi есть ряд математических функций, каторые могут справится с округлением. Можно еще применить всякие хитрости (допустим, предложенная мной проверка символа после запятой). Целью было узнать, почему так происходит. Почему Round округляет не так, как от нее ждут?

Есть у кого мнение по этому поводу?

Автор: <Spawn> 11.3.2005, 06:04
Цитата
Целью было узнать, почему так происходит. Почему Round округляет не так, как от нее ждут?

Это не ошбика Борладн это особенность представления дробных чисел в FPU, и, если я правильно помню, то именно от того, что 0.5(и некоторые другие, приведенные тобой числа), не могут быть представлены конечной двоичной дробью, в результате чего в реальности число ставновится чем то вроде - 0.499999999999999999999999999999999.

Где то на этом форуме недавно была ссылка на статью, хорошо это обьясняющюю, но найти ее, к сожалению, я не могу.

Автор: Alex 11.3.2005, 09:47
Цитата
Где то на этом форуме недавно была ссылка на статью, хорошо это обьясняющюю, но найти ее, к сожалению, я не могу.

http://forum.vingrad.ru/index.php?showtopic=44001&hl=

Автор: Akina 11.3.2005, 09:54
Цитата(Poseidon @ 11.3.2005, 05:05)
При этом 1,5 округляется до 2 и 2,5 до 2 !!! 3,5 до 4 и 4,5 до 4 !!! И так далее. Что это? Ошибка Borland? Или какая-то неизвестная тонкость функции Round?

Хотя это - следствие представления числа в бине, но попутно замечу, что есть термин "бухгалтерское округление" - то что ОБЯЗАНО использоваться в любых денежных расчетах. Так вот, по нему половинка округляется не вниз и не вверх, а к БЛИЖАЙШЕМУ ЧЕТНОМУ. И это определено соотв. стандартом, международным кстати. Вот такие пироги...

Автор: RST8 11.3.2005, 20:59
Так чем же можно округлить число по правилам до какого нибудь знака после запятой?

Автор: Петрович 11.3.2005, 22:13
Цитата(RST8 @ 11.3.2005, 21:59)
Так чем же можно округлить число по правилам до какого нибудь знака после запятой?

Дык неужели из темы не стало ясно что все зависит от того какие правила имеются ввиду. Много их, правил-то, и все разные.

Автор: SPrograMMer 11.3.2005, 22:45
Цитата
Дык неужели из темы не стало ясно что все зависит от того какие правила имеются ввиду. Много их, правил-то, и все разные.

smile
У меня что-то родилось:
Пусть наше 0.5 представляется в виде 0.499999999999999999999, а нам нужно всё это округлить до целых. Поступаем следующим образом:
1. Округлаем до того знака, до которого задавли число, то есть, в числе 0.5 одна цифра после запятой => округляем до одного знака после запятой (можно RondTo`ом воспользоваться).
Вот только что при этом получится? по идее 0.49999999999999 должно превратиться в 0.5
2. Округлаем (тут же) полученное число до нужного.

Код

  X:=0.5;
  Y:=Round(RoundTo(X,-1));  // оч хочется что б в y стало 1.


Автор: Петрович 12.3.2005, 00:40
Посмотри описания функций RoundTo и SimpleRoundTo:

Цитата
RoundTo uses “Banker’s Rounding” to determine how to round values that are exactly midway between the two values that have the desired number of significant digits. This method rounds to an even number in the case that AValue is not nearer to either value.

The following examples illustrate the use of RoundTo:

Expression Value

RoundTo(1234567, 3) 1234000
RoundTo(1.234, -2) 1.23
RoundTo(1.235, -2) 1.24
RoundTo(1.245, -2) 1.24

Note: The behavior of RoundTo can be affected by the Set8087CW procedure or SetRoundMode function.

Цитата
SimpleRoundTo uses asymmetric arithmetic rounding to determine how to round values that are exactly midway between the two values that have the desired number of significant digits. This method always rounds to the larger value.

The following examples illustrate the use of SimpleRoundTo:

Expression Value

SimpleRoundTo(1234567, 3) 1234000
SimpleRoundTo(1.234, -2) 1.23
SimpleRoundTo(1.235, -2) 1.24
SimpleRoundTo(-1.235, -2) -1.23


А если проще:

Функции RoundTo и SimpleRoundTo используют несколько разные алгоритмы в случаях, когда округляемое число расположено точно посередине между двумя значениями, имеющими заданное число значащих разрядов. Функция RoundTo в этом случае округляет до четного числа. А функция SimpleRoundTo в этом случае округляет до большего числа.

Так что выбирай то что тебе надо.

Автор: Santer 12.3.2005, 02:58
Вот вам еще один способ округления: к округляемому числу прибавляем 0.5 и отбрасываем дробную часть

Автор: Poseidon 12.3.2005, 04:37
Всем спасибо за ответы smile
В заключении темы хотелось бы все-таки выяснить, так есть ли в Delphi функция, каторая выполняла бы то, что задокументировано для Round ? Т.е. функция, каторая округляла бы по общепринятым правилам, а не по каким-либо бухгалтерским или еще. Чтоб <?,5 округляло к меншему, а >=?.5 - к большему. Или все таки придется написать такую функцию самому?


Santer
Цитата
Вот вам еще один способ округления: к округляемому числу прибавляем 0.5 и отбрасываем дробную часть
Интересный способ. Как говорится, все гениальное - просто. Не плохо начал (это ведь твое парвое сообщение)! Так и держи.

Автор: Alex 12.3.2005, 10:57
Цитата(Poseidon @ 12.3.2005, 04:37)
В заключении темы хотелось бы все-таки выяснить, так есть ли в Delphi функция, каторая выполняла бы то, что задокументировано для Round ? Т.е. функция, каторая округляла бы по общепринятым правилам, а не по каким-либо бухгалтерским или еще. Чтоб <?,5 округляло к меншему, а >=?.5 - к большему. Или все таки придется написать такую функцию самому?

Ты так и не понял проблемы.

Автор: Poseidon 12.3.2005, 20:17
Alex
Цитата
Ты так и не понял проблемы.
Пожет быть и не понял полностью. Вот что понял, так это то, что Round не округлит так, как в школах учат. Может потому, что
Цитата
половинка округляется не вниз и не вверх, а к БЛИЖАЙШЕМУ ЧЕТНОМУ
может потому, что
Цитата
0.5(и некоторые другие, приведенные тобой числа), не могут быть представлены конечной двоичной дробью, в результате чего в реальности число ставновится чем то вроде - 0.499999999999999999999999999999999.
может еще по какой причине.
Это действительно проблема, т.к. в книге по Delphi написано буквально следующее:
Цитата
  Функция Round(n) возврещает целое, полученное путем округления n по известным правилам
Может быть следует задать авторам этой книги вопрос, а что значит "известные правила"? Думаю это будет лишним. Если спросить у любого школьника (старших классов): "К чему округлиться 3,5?", то он сразу же ответит к 4." А к чему округлиться 3,4?" - "К трем." Это я думаю и есть известные правила.
Даже в справке Delphi написано
Цитата
Round returns an Int64 value that is the value of X rounded to the nearest whole number
На сколько я понял, перевод этого звучит так: "Round возвращает значение Int64, каторое является значением Х, округленным к самому близкому целому числу. Коментарии я думаю излишни.

PS. Хотелось бы все таки узнать, есть ли та функция, что округлит 3,5 к 4 ("по известным правилам"). Как видно Round не справляется.
Alex, лично к тебе: В одной из других тем (не так давно) ты писал следующее:
Цитата
А я в душе человек ленивый и ужасно не люблю изобретать велосипеды я ищу уже написанное стандартное и им пользуюсь (хотя иногда на поиски уходит не один месяц  ).
Так вот подскажи, может нашел что? Или может в FAQ что-нибудь есть по этому поводу? Написать такую функцию не проблема, но
Цитата
Дело в том, что достаточно грамотные и знающие программиста порой не знают о существовании таких нужных и полезных функций. Но их можно понять, эти функции появились в Delphi не так давно, по-моему, начиная с 5 версии

и
Цитата
Если уж учить так, учить и если показывать пример то самый правильный и может быть самый навороченный, но в нем учтены все подводные камни, о которых вам известно.


Как говорится: "Модератор всегда прав" (p0s0l)

Автор: Alex 12.3.2005, 21:23
Poseidon, я насколько понимаю, ты меня обвиняешь в том, что я тебе отвечаю не полностью на твой вопрос. Прошу заметить, что я не участвую в обсуждении этой проблемы. Я только дал ссылку на статью, о которой упомянул один из участников. Как говорится, не знаю досконально вопроса и не лезу.

Автор: Alex 12.3.2005, 22:03
Полазил я тут по сети и нашел ответ на вопрос.
Цитата
После  Set8087CW(Default8087CW or $0800) Round(2.5) возвращает 3, а Round(3.5) - 4. А после Set8087CW(Default8087CW and $F7FF)  Round(2.5) возвращает 2, а Round(3.5) - 4(т.н. "банковское округление"). По умолчанию этот флаг сброшен, т.е. работает "банковское округление".(Проверял на D3 и W98)
Когда я впервые с этим столкнулся, то подумал, что это "происки империалистов":o). Но с удивлением обнаружил в "Правилах округления": "Если отбрасываемая цифра 5, а за ней нет значащих цифр, то округление производится на ближайшее четное число"(© М.Я. Выгодский, "Справочник по элементарной математике", М., 1966)

Автор: Петрович 12.3.2005, 22:29
Цитата(Poseidon @ 12.3.2005, 21:17)
Даже в справке Delphi написано
Цитата
Round returns an Int64 value that is the value of X rounded to the nearest whole number

На сколько я понял, перевод этого звучит так: "Round возвращает значение Int64, каторое является значением Х, округленным к самому близкому целому числу. Коментарии я думаю излишни.

А следующее предложение в хелпе переводить не пробовал? Тогда помогу я smile
Цитата
If X is exactly halfway between two whole numbers, the result is always the even number.

Если X точно посередине между двумя числами, результат будет ЧЕТНЫМ числом.


Цитата(Poseidon @ 12.3.2005, 21:17)
Как видно Round не справляется.

Читайте внимательнее хелп!

Цитата(Poseidon @ 12.3.2005, 21:17)
PS. Хотелось бы все таки узнать, есть ли та функция, что округлит 3,5 к 4 ("по известным правилам").

Если имеются ввиду школьные правила, то читай внимательнее http://forum.vingrad.ru/index.php?showtopic=44984&view=findpost&p=348682

Автор: Alex 12.3.2005, 22:41
Poseidon, если ты задаешь вопрос, и тебе отвечают, то будь любезен проверять то, что тебе предлагают, а не орать на весь форум, что тебя обидели и все такие нехорошие.

Держи свой код smile
Код

var
  str: string;
  x,y: real;
begin
  x:= 0.5;
  y:= 1.5;
  str:= FloatToStr(SimpleRoundTo(x, 0) + SimpleRoundTo(y, 0));
  ShowMessage(str);
end;

Автор: Poseidon 13.3.2005, 00:47
Alex
Цитата
насколько понимаю, ты меня обвиняешь в том, что я тебе отвечаю не полностью на твой вопрос.
Цитата
если ты задаешь вопрос, и тебе отвечают, то будь любезен проверять то, что тебе предлагают, а не орать на весь форум, что тебя обидели и все такие нехорошие.
Принашу свои извенения, если кого-то ненароком зацепил словом или обидел. Я этого не хотел.
Цитата
а не орать на весь форум, что тебя обидели и все такие нехорошие
Вообще-то такого не было! Но я еще раз извеняюсь, если что-то написал не так.

Петрович
Цитата
Читайте внимательнее хелп!
Вот уж точно: "поспешишь - людей насмешишь". Да, во внимательность сила!

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

Автор: amsoft 10.12.2009, 08:04
Возвращаясь к старой теме:

Недавно тоже столкнулся с такой некорректной работой функции Round.
При округлении числа, дробная часть которого = 0.5 всегда возвращает четное число, это баг с точки зрения математики (тем более каждое неправильное округление в моем случае может обойтись в реальную "копеечку"). Но разработчикам дельфей видать не знакомы правила математики  smile 
Я победил это так:

Код

function MyRound(X: Real): Integer;
begin
Result := Trunc(X);
if (X - Result) >= 0.5 then Result := Result + 1;
end;


ЗЫ У меня D5, может в более поздних версиях нет такого бага

Автор: Демо 10.12.2009, 11:39
amsoft, 

Код

If X is exactly halfway between two whole numbers, the result is always the even number. 
This method of rounding is often called “Banker’s Rounding”.


Код

Round half to even

A tie-breaking rule that is even less biased is round half to even, namely
If the fraction of y is 0.5, then q is the even integer nearest to y.

Thus, for example, +23.5 becomes +24, +22.5 becomes +22, -22.5 becomes -22, and -23.5 becomes -24. This variant of the round-to-nearest method is also called unbiased rounding, convergent rounding, statistician's rounding, Dutch rounding, Gaussian rounding, or bankers' rounding. For most reasonable distributions of y values, the expected (average) value of the rounded numbers is essentially the same as that of the original numbers, even if the latter are all positive (or all negative). This is widely used in bookkeeping.


Так что никакого глюка. В Help именно так и описано поведение Round.

Автор: amsoft 10.12.2009, 11:52
Демо, 
я знаю - читал уже, но меня не устраивает такой метод округления.
Кстати, раз уж его называют "Banker’s Rounding", то могли бы его засунуть в функцию типа BankerRound, а Round сделали бы нормальным.

Чуть модернизировал, теперь работает с отрицательными числами тоже  smile 

Код

function SmartRound(X: Extended): Int64;
begin
Result := Trunc(X);
if Abs(X - Result) >= 0.5
then if X < 0
     then Dec(Result)
     else Inc(Result);
end;


ЗЫ Не побоюсь этого слова Smart  smile 

Автор: amsoft 10.12.2009, 12:53
кстати, есть ли в делфях уже готовая подобная функция, чтобы велосипедостроительством не заниматься?

Автор: profesiachuvak 10.12.2009, 13:43
Цитата(amsoft @ 10.12.2009,  12:53)
кстати, есть ли в делфях уже готовая подобная функция, чтобы велосипедостроительством не заниматься?

Да какой тут велосипед, ранее предлогалось сделать так
Код

var 
i,s:real;
begin
i:=2.5; /к примеру возьмём это число
s:=trunc(i+0.5);
end;


Вот и весь велосипед. Округляй с помощью trunc() и получишь нормальное математическое округление по школьным правилам.

Автор: amsoft 10.12.2009, 15:01
profesiachuvak, 

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

Thx

Автор: Alexeis 10.12.2009, 15:06
profesiachuvak, а если число отрицательное? 

Автор: Демо 10.12.2009, 16:59
Цитата(profesiachuvak @  10.12.2009,  13:43 Найти цитируемый пост)
Вот и весь велосипед. Округляй с помощью trunc() и получишь нормальное математическое округление по школьным правилам.


Этот велосипед для простейших случаев.


Цитата(profesiachuvak @  10.12.2009,  13:43 Найти цитируемый пост)
var i,s:real;begini:=2.5; /к примеру возьмём это числоs:=trunc(i+0.5);end;


Простой пример - 2.4(9), получающееся в процессе вычислений.

Автор: Beltar 10.12.2009, 18:27
Цитата

Округляй с помощью trunc() и получишь нормальное математическое округление по школьным правилам.


trunc(3.9)=3, а не 4.

Лучше уж frac(x)>0.5 проверять.

Автор: amsoft 10.12.2009, 18:30
Alexeis, 
немного подшаманить можно:

Код

if i < 0 
then Result := Trunc(i-0.5) 
else Result := Trunc(i+0.5)


Добавлено через 1 минуту и 2 секунды
Beltar, 
а прибавлять 0.5 кто будет?

Автор: ~FoX~ 10.12.2009, 18:33
Poseidon, Лениво читать весь тред, но имел счастья сталкнутся с обратной проблемой, вобшим не суть дело... Тип Currency даст тебе то что ты хочешь поиметь... 

Автор: Beltar 10.12.2009, 18:35
Чего прибавлять??

Код

function TheOnlyTrueRound(X:Extended):Integer;
begin
if frac(X)>=0.5 then {0.5 в двоичной вроде 0.1, конечная дробь} Result:=trunc(X)+1
  else Result:=trunc(X)-1;
end


Или я не прав:

Автор: Alexeis 10.12.2009, 18:38
Цитата(amsoft @  10.12.2009,  17:30 Найти цитируемый пост)
немного подшаманить можно:

  Можно, но тут уже добавилось ветвление и конструкция перестала быть изящной.

Автор: amsoft 10.12.2009, 18:47
Alexeis, ИМХО Без ветвления никуда  smile

Добавлено через 1 минуту и 17 секунд
Beltar, главное результат

ЗЫ мой прошлый велосипед с десятью колесами и двумя сиденьями в топку  smile 

Автор: bems 10.12.2009, 19:56
Модератор: amsoft, не нужно откапывать старые темы.




Автор: amsoft 11.12.2009, 07:01
bems, 
я вообще хотел создать новую тему, но нашел тему с похожей проблемой и решил "добить" ее до конца, ибо это меня тоже касается.

В принципе с этим вопросом разобрались.
Тема может быть закрыта.

Автор: Beltar 11.12.2009, 08:02
Что-то я туплю.

Код

function TheOnlyTrueRound(X:Extended):Integer;
begin
Result:=trunc(X)+Integer(frac(X)>=0.5);
end

Автор: bems 11.12.2009, 18:41
amsoft, ну это так, на будущее. Если бы я увидел в этом нарушение, было бы предупреждение в личное дело. В данном случае поднятие топика оправдано, да.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)