Ceil непонятная обработка выражения

Вопросы программирования и использования среды Lazarus.

Модератор: Модераторы

Ответить
resident
энтузиаст
Сообщения: 605
Зарегистрирован: 13.03.2013 16:58:51

Ceil непонятная обработка выражения

Сообщение resident »

Здрасть :)
Отдельные значения Ceil обрабатывает нормально. Но с выражениями - дробями переменных считает по разному. Подскажите в чем дело?

Пример:
Ceil((A - B)/C)

Код: Выделить всё

unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,
  StdCtrls, Math;

type

  { TfmMain }

  TfmMain = class(TForm)
    btCalculate: TButton;
    leA: TLabeledEdit;
    leB: TLabeledEdit;
    leC: TLabeledEdit;
    leD: TLabeledEdit;
    leE: TLabeledEdit;
    leF: TLabeledEdit;
    procedure btCalculateClick(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
  end;

var
  fmMain: TfmMain;

implementation

{$R *.lfm}

{ TfmMain }

procedure TfmMain.btCalculateClick(Sender: TObject);
  var
    A, B, C, D, E, F: real;
  begin
    A := StrToFloat(leA.Text);
    B := StrToFloat(leB.Text);
    C := StrToFloat(leC.Text);
    D := (A - B)/C;
    E := Ceil((A - B)/C);
    F := Ceil(D);
    leD.Text := FloatToStr(D);
    leE.Text := FloatToStr(E);
    leF.Text := FloatToStr(F);
  end;

end. 

Понятней наверное картинками:
1) С целыми числами - всё Ок
2) Уменьшаю в 10 раз - всё Ок
3) Уменьшаю еще в 10 раз - получаю отличие, значение ошибочно увеличивается на единицу
Изображение

Добавлено спустя 21 минуту 55 секунд:
Ухахочешься, уменьшил значения еще в 10 раз. Опять верный ответ (4).
Изображение
Так что получается, что при каких-то значениях Ceil неправильно обработал выражение.
Что же теперь делать, свою функцию чтоль писать? Но на основе чего? Этих же глюченных функций :)

Добавлено спустя 7 минут 8 секунд:
Вот проект
http://listingimg.s3.amazonaws.com/test.zip
zub
долгожитель
Сообщения: 2890
Зарегистрирован: 14.11.2005 22:51:26
Контактная информация:

Сообщение zub »

>>Что же теперь делать, свою функцию чтоль писать? Но на основе чего? Этих же глюченных функций
Что делать?, всё пропало!!111))
Если нечего делать, то нужно почитать чтонибудь умное. например про особенности операций с плавающей точкой.
В краце суть примерно такая:
Для флоатов и прочих дублей и екстендовт в результате операций над ними никогда не будет ровного значени например 4 как вы подразумеваете.
будет 3.999много9 или 4.0000много01 т.е. очень близко, но неровно.
и это всегда нужно учитывать, способы описаны в множестве статей любезно находимых гуглем

в вашем случае всё верно, Ceil как и задумано округлил 4.0много01 к ближайшему большему, т.е. =5

Добавлено спустя 5 минут 21 секунду:
>>над ними никогда не будет ровного значени например
тут я конечно вру, бывает, но ооочень редко и на это не нужно расчитывать
resident
энтузиаст
Сообщения: 605
Зарегистрирован: 13.03.2013 16:58:51

Сообщение resident »

Нда, точно, вставил еще такую строчку

Код: Выделить всё

if Frac((A - B)/C) > 0 then ShowMessage('Шеф! Всё пропало!');

И выяснилось, что сюрпризы даже там, где вроде и сейчас правильно считает.
Так что делать? :)
Мне точность не нужна. Там сотые максимум играют роль.
А если округлять дробь, например, до десятитысячных и потом уже подавать её в функцию Ceil?
zub
долгожитель
Сообщения: 2890
Зарегистрирован: 14.11.2005 22:51:26
Контактная информация:

Сообщение zub »

ввести в программу еще переменную, задающую допустимую погрешность, например:

Код: Выделить всё

const
  delta=e-10;

в случае если таки важен ceil делать так:

Код: Выделить всё

E := Ceil((A - B)/C-delta);
F := Ceil(D-delta);


погуглил за тебя, спасибо можешь не говорить:
http://www.delphikingdom.com/asp/viewit ... alogid=374
resident
энтузиаст
Сообщения: 605
Зарегистрирован: 13.03.2013 16:58:51

Сообщение resident »

zub писал(а):в случае если таки важен ceil делать так

А вот если значение дроби (A - B)/C будет равно = 4.0много01 + delta
Тогда значение в Ceil-е будет опять же равно 4.0много01 и опять будет та же ошибка.
Поэтому я и спросил про функцию округления, может будет вернее округлять до тех же e-10? А потом уже округленное значение подсовывать в Ceil.

zub писал(а):погуглил за тебя, спасибо можешь не говорить

О Боже, как давно это было. "Сейчас во Флориде и в Королевстве" эпоха застоя.
Ок, тогда пиши Paypal адрес :)
zub
долгожитель
Сообщения: 2890
Зарегистрирован: 14.11.2005 22:51:26
Контактная информация:

Сообщение zub »

>>А вот если значение дроби (A - B)/C будет равно = 4.0много01 + delta
>>Тогда значение в Ceil-е будет опять же равно 4.0много01 и опять будет та же ошибка.
А откуда я знаю что в конечном итоге нужно получить? гугль знает, я нет)) Природа "ошибки" понятна? - Шеф! не всё пропало!
это общий принцип работы с вещественными числами, всегда давать некоторую разбежку, покрывающую "погрешность" проца, но укладывающуюся в точность задачи

>>4.0много01 + delta
ну дак это число больше 4 на значение большее чем допустимая нами погрешность, соответственно его и цилкать до пяти надо, а никак не до четырех.
alexey38
долгожитель
Сообщения: 1627
Зарегистрирован: 27.04.2011 19:42:31

Сообщение alexey38 »

resident писал(а):Поэтому я и спросил про функцию округления

Посмотрите на Round или RoundTo, ибо Ceil округляет число в сторону увеличения, а round до ближайшего целого..
Alex2013
долгожитель
Сообщения: 3237
Зарегистрирован: 03.04.2013 11:59:44

Сообщение Alex2013 »

Если нужно ПОКАЗАТЬ сугубо до сотых то вроде Str (FolatNum:1:2,S ) никто не отменял ... :idea:
resident
энтузиаст
Сообщения: 605
Зарегистрирован: 13.03.2013 16:58:51

Сообщение resident »

zub писал(а):это общий принцип работы с вещественными числами, всегда давать некоторую разбежку, покрывающую "погрешность" проца, но укладывающуюся в точность задачи

Понятно.
Провожу округление до нужной точности и ответ подаю в Ceil, вроде работает.

zub писал(а):ну дак это число больше 4 на значение большее чем допустимая нами погрешность, соответственно его и цилкать до пяти надо, а никак не до четырех.

Да, точно. У меня уже мозги заклинило вчера.

zub писал(а):Природа "ошибки" понятна? - Шеф! не всё пропало!

Да, спасиб :)
alexey38
долгожитель
Сообщения: 1627
Зарегистрирован: 27.04.2011 19:42:31

Сообщение alexey38 »

resident писал(а):Провожу округление до нужной точности и ответ подаю в Ceil, вроде работает.

Все таки я Вам предлагаю не использовать Ceil (я эту функцию в своей практике например никогда не использовал), а посмотреть в сторону Round или RoundTo.

1.function Round ( const Number : Extended ) : Int64;
Функция Round округляет число с плавающей запятой (Number) до целого значения.

2. function Trunc ( const Number : Extended ) : Integer;
Функция Trunc возвращает целочисленную часть числа с плавающей запятой.

3. Функция Ceil( X: Extended ): Integer;
Функция округляет значение аргумента X к ближайшему боль-шему целому. Абсолютное значение X не должно превышать величину MaxInt ( 2 147 483 647 ).

4. Функция Floor( X: Extended ): Integer;
Функция округляет значение аргумента X к ближайшему меньшему целому. Абсолютное значение X не должно превышать величину MaxInt ( 2 147 483 647 ).
Аватара пользователя
debi12345
долгожитель
Сообщения: 5761
Зарегистрирован: 10.05.2006 23:41:15
Откуда: Ташкент (Узбекистан)

Сообщение debi12345 »

С CURRENCY-типом все четко:

Код: Выделить всё

program ceiltest;

uses sysutils, math;

var
  A,B,C,D,E,F: currency;
begin
  A:= 10;
  B:= 2;
  C:= 2;
  D := (A - B)/C;
  E := Ceil((A - B)/C);
  F := Ceil(D);
  writeln('1:1-> (A - B)/C',D);
  writeln('1:1-> Ceil((A - B)/C',E);
  writeln('1:1-> Ceil(D)',F);

  A:= 1;
  B:= 0.2;
  C:= 0.2;
  D := (A - B)/C;
  E := Ceil((A - B)/C);
  F := Ceil(D);
  writeln('1:10-> (A - B)/C',D);
  writeln('1:10-> Ceil((A - B)/C',E);
  writeln('1:10-> Ceil(D)',F);

  A:= 0.1;
  B:= 0.02;
  C:= 0.02;
  D := (A - B)/C;
  E := Ceil((A - B)/C);
  F := Ceil(D);
  writeln('1:100-> (A - B)/C',D);
  writeln('1:100-> Ceil((A - B)/C',E);
  writeln('1:100-> Ceil(D)',F);

end.

1:1-> (A - B)/C 4.000000000000000000E+00
1:1-> Ceil((A - B)/C 4.000000000000000000E+00
1:1-> Ceil(D) 4.000000000000000000E+00
1:10-> (A - B)/C 4.000000000000000000E+00
1:10-> Ceil((A - B)/C 4.000000000000000000E+00
1:10-> Ceil(D) 4.000000000000000000E+00
1:100-> (A - B)/C 4.000000000000000000E+00
1:100-> Ceil((A - B)/C 4.000000000000000000E+00
1:100-> Ceil(D) 4.000000000000000000E+00

вопрос в допустимом диапазоне значений для этого типа...

Добавлено спустя 15 минут 28 секунд:
В данном тесте диапазон значений CURRENCY ограничен :
+-920,000,000,000,000.999999..(конца и края не видно - поэтому и нет ошибок округления);

то есть 921 квинтиллион. Так что надо смотреть реально используемый оный в вашей задаче

Добавлено спустя 8 минут 5 секунд:
Хм,.. Используя гигантское допустимое количестов знаков после запятой у типа CURRENCY, в нем можно хранить(предварительно деля) гораздо большие числа, у которых после точки мало знаков. Надо взять на заметку :)

Добавлено спустя 25 минут 6 секунд:
Особенно прикололи правильные значение после очень lossy-преобразований со степенями

Код: Выделить всё

  
  A:= power(power(0.001,1/909),909);
  B:= power(power(0.0002,1/113),113);
  C:= power(power(0.0002,1/2),2);
 /code]
или чтобы исключить оптимизатор :
[code]
  A:= power(0.001,1/909;
  A:= power(A,909;

  B:= power(0.0002,1/113);
  B:= power(B,113);

  C:= power(0.0002,1/2);
  C:= power(C,2);


одно "но" - больше знаков после запятой в этом случае использовать нельзя - иначе числа конвертятся в ноль. Познавательно, однако :)
Ответить