Модераторы: Poseidon, Snowy, bems, MetalFan
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Округление 
V
    Опции темы
Burka
Дата 20.1.2010, 15:22 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 429
Регистрация: 11.5.2007

Репутация: нет
Всего: 2



Давно бьюсь над процедурой округления, которая будет правильно округлять все числа, по школьному правилу. 
Сейчас использую такую процедуру.
Код

function RoundEx(X: Double; Precision: Integer ): Double;
var
ScaledFractPart, Temp: Double;
begin
if x=0 then
 begin
 RoundEx:=0;
 exit;
 end;
TRY
ScaledFractPart := Frac(X) * Precision;
Temp := Frac(ScaledFractPart);
ScaledFractPart := Int(ScaledFractPart);
if Temp >= 0.5 then
ScaledFractPart := ScaledFractPart + 1;
if Temp <= -0.5 then
ScaledFractPart := ScaledFractPart - 1;
RoundEx := Int(X) + ScaledFractPart / Precision;
EXCEPT
ShowMessage('Îøèáêà îêðóãëåíèÿ '+FloatToStr(x));
END
end;

Не давно нашел такой баг в ней
при округлении допустим числа -12.15 до десятых получаем -12.2 (правильно)
но округляя -22.15 получаем -22.1 (не правильно)

Подскажите есть ли стабильные процедуры для этого или может что в моей исправить


--------------------
Великие умы обсуждают идеи; средние умы обсуждают события; мелкие умы обсуждают людей.
PM MAIL   Вверх
XperT
Дата 21.1.2010, 14:52 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 269
Регистрация: 19.8.2006

Репутация: 2
Всего: 4



Посмотри в сторону Math.SimpleRoundTo
PM MAIL   Вверх
Burka
Дата 25.1.2010, 09:51 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 429
Регистрация: 11.5.2007

Репутация: нет
Всего: 2



пока округляю с помощью FormatFloat и вроде норм


--------------------
Великие умы обсуждают идеи; средние умы обсуждают события; мелкие умы обсуждают людей.
PM MAIL   Вверх
shmelina
Дата 27.1.2010, 02:49 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 66
Регистрация: 5.6.2009

Репутация: нет
Всего: нет



SimpleRoundTo тоже неправильно округляет. Сам проверил.

Вообще с округлением не все так просто. Я когда прочитал эту статью, мне плохо стало: 
Загадки округления. http://www.delphikingdom.com/asp/viewitem.asp?catalogID=1217

Я пока остановился на этом способе:
Trunc(Value * 100 + 0.5) / 100
PM MAIL   Вверх
Burka
Дата 28.1.2010, 11:27 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 429
Регистрация: 11.5.2007

Репутация: нет
Всего: 2



shmelina, спасибо тоже попробовал твой метод, отрицательные числа он не правильно округляет я немного доработал) лишь бы работало)
Код

function RoundEx(x:double; Precision:integer):double;
var mz:boolean;
begin
if Precision=0 then Precision:=1;
if x<0 then mz:=true else mz:=false;
if mz then x:=x*-1;
result:=Trunc(x * Precision + 0.5) / Precision ;
if mz then result:=result*-1;
end;


А вообще надо найти или написать уже корректную процедурку


--------------------
Великие умы обсуждают идеи; средние умы обсуждают события; мелкие умы обсуждают людей.
PM MAIL   Вверх
shmelina
Дата 28.1.2010, 15:31 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 66
Регистрация: 5.6.2009

Репутация: нет
Всего: нет



Число 1.15 округляется до 1.1 smile
PM MAIL   Вверх
shmelina
Дата 28.1.2010, 17:11 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 66
Регистрация: 5.6.2009

Репутация: нет
Всего: нет



Странно. Поднял свои исследования на тему округления. Даже тогда написал специальную программу, которая округляет разными способами. И точно помню, что пришел к выводу, что есть проблемы с функцией SimpleRoundTo. Проверял на D2009. Но сейчас не могу вспомнить какие именно.
Сейчас все пробую и вроде все правильно работает. Единственное, что нашел, что в D7 реализация функции SimpleRoundTo на отрицательных числах будет ошибочная:
Код

function SimpleRoundTo(const AValue: Double; const ADigit: TRoundToRange = -2): Double;
var
  LFactor: Double;
begin
  LFactor := IntPower(10, ADigit);
  Result := Trunc((AValue / LFactor) + 0.5) * LFactor;
end;

В более поздних версиях (турбо (2006), 2007) сделали уже правильно:
Код

function SimpleRoundTo(const AValue: Double; const ADigit: TRoundToRange = -2): Double;
var
  LFactor: Double;
begin
  LFactor := IntPower(10, ADigit);
  if AValue < 0 then
    Result := Trunc((AValue / LFactor) - 0.5) * LFactor
  else
    Result := Trunc((AValue / LFactor) + 0.5) * LFactor;
end;

Если смотреть на исследования в статье, то такое ощущение, что в функции SimpleRoundTo не было "+ 0.5". Может в патче потом добавили?

Сейчас не важно.

Пока такой вывод: функция SimpleRoundTo работает правильно, если она такая:
Код

function SimpleRoundTo(const AValue: Double; const ADigit: TRoundToRange = -2): Double;
var
  LFactor: Double;
begin
  LFactor := IntPower(10, ADigit);
  if AValue < 0 then
    Result := Trunc((AValue / LFactor) - 0.5) * LFactor
  else
    Result := Trunc((AValue / LFactor) + 0.5) * LFactor;
end;


Верно?
PM MAIL   Вверх
shmelina
Дата 28.1.2010, 19:11 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 66
Регистрация: 5.6.2009

Репутация: нет
Всего: нет



Так. Снова погрузился в проблемы округления. Вот что выяснилось на этот раз: Результаты округления зависят
1. от типа данных переменной, которую округляем.
2. от типа данных параметров функции округления SimpleRoundTo.

В Delphi 2007 функция объявлена так:
function SimpleRoundTo(const AValue: Double; const ADigit: TRoundToRange = -2): Double;
В Delphi 2010 функция объявлена так:
function SimpleRoundTo(const AValue: Extended; const ADigit: TRoundToRange = -2): Extended;

Вроде бы тип данных сменился и только! Но как оказалось это влияет на результат - смотря что вы в нее передадите.

И так я протестировал две реализации функции SimpleRoundTo (Double и Extended) и округление прямо в коде: Trunc(Value * 10 + 0.5) / 10.
Тестировал на Delphi 2010. Три разных типа данных: Currenty, Double и Extended. Округлял число 1.15 до одного знака после запятой.

Вот что получилось:
Код

1,15       MyRound DoubleRound ExtendedRound
Currency       1,2         1,1           1,2
Double         1,1         1,1           1,1
Extended       1,2         1,1           1,2


А вот с числом 1,25
Код

1,25       MyRound DoubleRound ExtendedRound
Currency       1,3         1,2           1,3
Double         1,3         1,2           1,3
Extended       1,3         1,2           1,3


С отрицательными числами такая же картина.

Вывод: 
1. Переменную типа Double с помощью функции SimpleRoundTo корректно округлить нельзя.
2. Переменную любого типа с помощью функции SimpleRoundTo, где параметр передается как Double, корректно округлить нельзя.

Короткий вывод: Не используйте Double в округлениях smile

Вот код, которым я все это проверил. Положите на форму StringGrid и дайте ему имя sg.
Код

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, Grids;

type
  TForm1 = class(TForm)
    sg: TStringGrid;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

uses Math;

{$R *.dfm}

function SimpleRoundToDouble(const AValue: Double; const ADigit: TRoundToRange = -2): Double;
var
  LFactor: Double;
begin
  LFactor := IntPower(10, ADigit);
  if AValue < 0 then
    Result := Trunc((AValue / LFactor) - 0.5) * LFactor
  else
    Result := Trunc((AValue / LFactor) + 0.5) * LFactor;
end;

function SimpleRoundToExtended(const AValue: Extended; const ADigit: TRoundToRange = -2): Extended;
var
  LFactor: Extended;
begin
  LFactor := IntPower(10, ADigit);
  if AValue < 0 then
    Result := Trunc((AValue / LFactor) - 0.5) * LFactor
  else
    Result := Trunc((AValue / LFactor) + 0.5) * LFactor;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  ValCur, ResCur: Currency;
  ValDbl, ResDbl: Double;
  ValExt, ResExt: Extended;
begin
  sg.Align := alClient;
  sg.DefaultColWidth := 80;
  sg.DefaultRowHeight:= 18;
  sg.ColCount := 4;
  sg.RowCount := 8;
  //
  ValCur := 1.15;
  ValDbl := 1.15;
  ValExt := 1.15;
  //
  sg.Cells[0, 0] := '1,15';
  sg.Cells[1, 0] := 'MyRound';
  sg.Cells[2, 0] := 'DoubleRound';
  sg.Cells[3, 0] := 'ExtendedRound';
  sg.Cells[0, 1] := 'Currency';
  sg.Cells[0, 2] := 'Double';
  sg.Cells[0, 3] := 'Extended';
  // MyRound
  sg.Cells[1, 1] := FloatToStr(Trunc(ValCur * 10 + 0.5) / 10);
  sg.Cells[1, 2] := FloatToStr(Trunc(ValDbl * 10 + 0.5) / 10);
  sg.Cells[1, 3] := FloatToStr(Trunc(ValExt * 10 + 0.5) / 10);
  // DoubleRound
  sg.Cells[2, 1] := FloatToStr(SimpleRoundToDouble(ValCur, -1));
  sg.Cells[2, 2] := FloatToStr(SimpleRoundToDouble(ValDbl, -1));
  sg.Cells[2, 3] := FloatToStr(SimpleRoundToDouble(ValExt, -1));
  // ExtendedRound
  sg.Cells[3, 1] := FloatToStr(SimpleRoundToExtended(ValCur, -1));
  sg.Cells[3, 2] := FloatToStr(SimpleRoundToExtended(ValDbl, -1));
  sg.Cells[3, 3] := FloatToStr(SimpleRoundToExtended(ValExt, -1));
  // Отрицательные числа
  ValCur := -1.15;
  ValDbl := -1.15;
  ValExt := -1.15;
  //
  sg.Cells[0, 4] := '-1,15';
  sg.Cells[0, 5] := 'Currency';
  sg.Cells[0, 6] := 'Double';
  sg.Cells[0, 7] := 'Extended';
  // MyRound
  sg.Cells[1, 5] := FloatToStr(Trunc(ValCur * 10 - 0.5) / 10);
  sg.Cells[1, 6] := FloatToStr(Trunc(ValDbl * 10 - 0.5) / 10);
  sg.Cells[1, 7] := FloatToStr(Trunc(ValExt * 10 - 0.5) / 10);
  // DoubleRound
  sg.Cells[2, 5] := FloatToStr(SimpleRoundToDouble(ValCur, -1));
  sg.Cells[2, 6] := FloatToStr(SimpleRoundToDouble(ValDbl, -1));
  sg.Cells[2, 7] := FloatToStr(SimpleRoundToDouble(ValExt, -1));
  // ExtendedRound
  sg.Cells[3, 5] := FloatToStr(SimpleRoundToExtended(ValCur, -1));
  sg.Cells[3, 6] := FloatToStr(SimpleRoundToExtended(ValDbl, -1));
  sg.Cells[3, 7] := FloatToStr(SimpleRoundToExtended(ValExt, -1));
end;

end.


Не судите строго, если я где-то ошибся в своих исследованиях. Поправьте.
PM MAIL   Вверх
Burka
Дата 4.3.2010, 15:15 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 429
Регистрация: 11.5.2007

Репутация: нет
Всего: 2



Цитата

shmelina, Короткий вывод: Не используйте Double в округлениях smile

Я думаю логичнее было бы не использовать SimpleRoundTO ^^ и вообще лучше ничего не использовать из стандартных процедур для округления при серъезных подсчетах где важны погрешности.

А вообще я сейчас пользуюсь этим, и радуюсь) Юзеры не приходили пока с жалобами)

Код

//Precision количество знаков после запятой (0,1,2..)
function RoundEx(X: Extended; Precision: Integer ): Double;
 var s,tmpresult: String;
     i: Integer;
begin

 s:=FloatToStr(x);

 TMPResult:=FloatToStr(Round(StrToFloat(s)*exp(i*ln(10)))/(exp(i*ln(10))));
 Result:=StrToFloat(TmpResult);
end;

вообще она возращала строковую но мне не удобно так)

Это сообщение отредактировал(а) Burka - 4.3.2010, 15:17


--------------------
Великие умы обсуждают идеи; средние умы обсуждают события; мелкие умы обсуждают людей.
PM MAIL   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Delphi: Общие вопросы"
SnowyMetalFan
bemsPoseidon
Rrader

Запрещается!

1. Публиковать ссылки на вскрытые компоненты

2. Обсуждать взлом компонентов и делиться вскрытыми компонентами

  • Литературу по Дельфи обсуждаем здесь
  • Действия модераторов можно обсудить здесь
  • С просьбами о написании курсовой, реферата и т.п. обращаться сюда
  • Вопросы по реализации алгоритмов рассматриваются здесь
  • 90% ответов на свои вопросы можно найти в DRKB (Delphi Russian Knowledge Base) - крупнейшем в рунете сборнике материалов по Дельфи


Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Snowy, MetalFan, bems, Poseidon, Rrader.

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Delphi: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.0531 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.