function MyRoundTo1(const AValue: Double; const ADigit: TRoundToRange = -2): Double;
var
D, V: Double;
i: Integer;
begin
D := IntPower(10, -1 * (ADigit - 1));
V := AValue * D;
i := Trunc(V);
V := AValue;
if (i mod 10) = 5 then V := V + 1 / D;
Result := RoundTo(V, ADigit);
end;
function MyRoundTo2(const AValue: Double; const ADigit: TRoundToRange = -2): Double;
var
D, V: Double;
i: Integer;
begin
D := IntPower(10, -1 * (ADigit - 1));
i := Trunc(AValue * D);
V := AValue;
if (i mod 10) = 5 then V := V + 1 / D;
Result := RoundTo(V, ADigit);
end;
begin
ShowMessageFmt('MyRoundTo1:'#10'%g'#10'%g'#10#10'MyRoundTo2:'#10'%g'#10'%g',
[MyRoundTo1(24.2349999), MyRoundTo1(24.235), // ответ 24.23 и 24.24
MyRoundTo2(24.2349999), MyRoundTo2(24.235)]); // ответ 24.23 и 24.23
end;
Вопрос следующий - почему в принципе одинаковый код дает разный результат?
PS Отличие в том, что в MyRoundTo1 используется промежуточная переменная V := AValue * D, а в MyRoundTo2 выражение прямо в параметре функции Trunc
Уважаемые авторы вопросов! Большая просьба сообщить о результатах решения проблемы на этой странице. Иначе, следящие за обсуждением, возможно имеющие аналогичные проблемы, не получают ясного представления об их решении. А авторы ответов не получают обратной связи. Что можно расценивать, как проявление неуважения к отвечающим от автора вопроса.
16-03-2006 09:19 | Сообщение от автора вопроса
А поделиться самописными? Очень нужно - и не только мне... Если появится статья и не трудно, сообщи мне, пажалуйста. Почитаю - "больная" тема
Да мне все равно что "бухгалтерское", что "арифметическое" - главное чтоб считало также как в Excel'е...
Тогда речь идет об арифметическом округлении. Рассказывать зачем нужно бухгалтерское нужно на примере, у меня есть материал, в ближайшее время хочу подготовить статью. И заодно хочу потестировать функции округления, как встроенные, так и самописные.
Да мне все равно что "бухгалтерское", что "арифметическое" - главное чтоб считало также как в Excel'е... К стати, с Extended не работает верно, например MyRoundTo2(24.255) рузультат будет 24.25, а нужно 24.26...
Вот еще вариант округления:
function MyRoundTo(const AValue: Double; const ADigit: TRoundToRange = -2): Double;
var
RM: TFPURoundingMode;
PM: TFPUPrecisionMode;
D: Double;
begin
RM := GetRoundMode;
PM := GetPrecisionMode;
try
SetPrecisionMode(pmDouble);
D := IntPower(10, -1 * (ADigit - 1));
D := D * AValue;
if Trunc(D) mod 10 = 5
then SetRoundMode(rmUp)
else SetRoundMode(rmNearest);
Result := RoundTo(AVAlue, ADigit);
finally
SetRoundMode(RM);
SetPrecisionMode(PM);
end;
end;
Но это же у тебя арифметическое округление получилось, а не бухгалтерское - что 0.025, что 0.035 вверх округляет. Таким деньги не всегда считать можно.
Спасибо, Антон. Это у я никак с округлением не доразберусь... Поменял все на Extended - работает как нужно... Сбило с толку то, что в Math все RoundTo работают с Double
Всё дело в том, что у функции Trunc параметр имеет тип Extended. При вычислении её аргумента значения AValue и D сначала расширяются до Extended, а потом перемножаются (так работает процессор), и результат без искажений передаётся в Trunc. А ваша переменная имеет тип Double, и когда получившееся в результате вычисления произведения значение записывается в неё, самые младшие разряды отбрасываются. Потом-то, конечно, это значение при передаче его в Trunc снова расширяется до Extended, но младшие разряды уже содержат нули, а не то, что там было. Это даёт погрешность порядка 2^(-54). Вам просто повезло - в наткнулись на случай, когда такая маленькая погрешность влияет на исход дела.
P.S. Честно говоря, я так и не понял, зачем вообще нужны Single и Double там, где нет нужды экономить память. С Extended работать гораздо приятнее.
Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter. Функция может не работать в некоторых версиях броузеров.