ошибки округления в TDateTime?
От: svg2003  
Дата: 26.08.08 02:12
Оценка:
похоже вылез очередной баг VCL-ля.
исходная задача: имеем TDateTime, из него нужно получить количество дней. компилятор bcc5.5.1.
логично было бы преобразовать его в int и в нём будет лежать количество дней, но cледующий код выдаёт:

31.12.1899
0.9999999999999995559
0

void Test()
{
    TDateTime t1 = 0;
    TDateTime t2( "01:00:00" );
    for(int i = 0; i < 24; ++i)
    {
        t1 += t2;
    }

    cout << t1.DateTimeString().c_str() << endl;
    cout << setprecision(30) << double( t1 ) << endl;
    cout << int( t1 ) << endl;
}


хотелось бы знать, где я ошибся, и что я делаю не так, а то у меня всё большее желание, отказаться от этого самого TDateTime во всех проектах в пользу самописного класса. на сравнениях уже накалывался, теперь вот это
Re: ошибки округления в TDateTime?
От: Rius Россия  
Дата: 26.08.08 02:52
Оценка:
Здравствуйте, svg2003, Вы писали:

S>похоже вылез очередной баг VCL-ля.

S>исходная задача: имеем TDateTime, из него нужно получить количество дней. компилятор bcc5.5.1.
S>логично было бы преобразовать его в int и в нём будет лежать количество дней, но cледующий код выдаёт:

S>31.12.1899

S>0.9999999999999995559
S>0

S>
S>void Test()
S>{
S>    TDateTime t1 = 0;
S>    TDateTime t2( "01:00:00" );
S>    for(int i = 0; i < 24; ++i)
S>    {
S>        t1 += t2;
S>    }

S>    cout << t1.DateTimeString().c_str() << endl;
S>    cout << setprecision(30) << double( t1 ) << endl;
S>    cout << int( t1 ) << endl;
S>}
S>


S>хотелось бы знать, где я ошибся, и что я делаю не так, а то у меня всё большее желание, отказаться от этого самого TDateTime во всех проектах в пользу самописного класса. на сравнениях уже накалывался, теперь вот это


попробуйте получить число дней встроенными свойствами класса TDateTime
Re[2]: ошибки округления в TDateTime?
От: svg2003  
Дата: 26.08.08 02:58
Оценка:
Здравствуйте, Rius, Вы писали:

R>попробуйте получить число дней встроенными свойствами класса TDateTime


К сожалению, встроенных свойств для этого нету. В хелпе так и написано, The integral part of a TDateTime value is the number of days that have passed since 12/30/1899. Сейчас получается дикий изврат, когда я отбрасываю ошибки округления, а только потом получаю верное количество дней:


    // убрать погрешности вычисления времени
    TDateTime RemoveFloatRemsFromTime(const TDateTime &Time)
    {
        unsigned short d, m, y, h, n, s, ms;
        Time.DecodeTime( &h, &n, &s, &ms );
        Time.DecodeDate( &y, &m, &d );

        TDateTime r( y, m, d );
        ReplaceTime( r, TDateTime( h, n, s, ms ) );
        return r;
    }

    // получить количество дней в дате
    // нельзя использовать просто int из за возможных погрешностей вычислений
    int DaysCountFromDateTime(const TDateTime &Date)
    {
        int    r = RemoveFloatRemsFromTime( Date );
        return r;
    }
Re: ошибки округления в TDateTime?
От: Hruks Россия www.hruks.com
Дата: 26.08.08 06:02
Оценка:
Здравствуйте, svg2003, Вы писали:

S>похоже вылез очередной баг VCL-ля.


int(0.9999999999999995559) и должен быть равен 0. Тут как раз всё верно.

Единственный ньюанс это небольшая неточность при конвертации из строки в число: TDateTime t2( "01:00:00" );
Но это вполне допустимо.
Работа с плавающей точкой требует аккуратности.
Например нельзя сравнивать числа с плавающей точкой на равестнство.
Вместо этого нужно сравнивать модуль разности чисел с константой точности.

Для Вашего примера характерна ещё одна неосторожность в работе с плавающей точкой — целочисленное откидывание дробной части (преобразование к инту)
Либо прибавляйте 0.5 к переменной t1 перед преобразованием к int, либо используйте функцию округления (trunc) вместо отбрасывания целой части (int).
Re[2]: ошибки округления в TDateTime?
От: andy1618 Россия  
Дата: 26.08.08 06:43
Оценка:
H>Либо прибавляйте 0.5 к переменной t1 перед преобразованием к int, либо используйте функцию округления (trunc) вместо отбрасывания целой части (int).

trunc — это, вроде, тоже отбрасывание. Надо round использовать. А вообще, есть такой чудный модуль — DateUtils (по крайней мере, в Delphi) — там разных функций для работы с TDateTime — мама не горюй!
Re: ошибки округления в TDateTime?
От: andy1618 Россия  
Дата: 26.08.08 06:47
Оценка: :)
S>похоже вылез очередной баг VCL-ля.



По-видимому, Вам сюда:
(статья 7-летней давности про "неочевидные особенности вещестенных чисел")
http://www.delphikingdom.com/asp/viewitem.asp?catalogid=374
Re[3]: ошибки округления в TDateTime?
От: Leonid Troyanovsky  
Дата: 26.08.08 09:27
Оценка:
Здравствуйте, andy1618, Вы писали:

A>trunc — это, вроде, тоже отбрасывание. Надо round использовать. А вообще, есть такой чудный модуль — DateUtils (по крайней мере, в Delphi) — там разных функций для работы с TDateTime — мама не горюй!


А чего в оном сверхестественного?
Смотрим function DaysBetween и видим тот же Trunc.
--
С уважением, LVT
Re: ошибки округления в TDateTime?
От: svg2003  
Дата: 26.08.08 23:03
Оценка:
Здравствуйте, svg2003, Вы писали:

Ребят, я всё прекрасно понимаю про внутреннюю структуру вещественных чисел, особенности преобразования и т.д. и т.п. Но у меня-то задача конкретная — получить из TDateTime число дней. Способ, предложенный в документации, работает не верно. Кстати, +0.5 делать тоже нельзя, потому как 23:00:00 в вещественном виде представлен 0.9583333333333329263. Конечно, можно делать +0.000005 или нечто подобное, что сделал я с пересозданием TDateTime-а, но проблему-то это не решает по сути. Ну нет ошибки в том месте, вылезет потом где-нибудь в другом
Re[4]: ошибки округления в TDateTime?
От: andy1618 Россия  
Дата: 27.08.08 05:51
Оценка:
A>>trunc — это, вроде, тоже отбрасывание. Надо round использовать. А вообще, есть такой чудный модуль — DateUtils (по крайней мере, в Delphi) — там разных функций для работы с TDateTime — мама не горюй!

LT>А чего в оном сверхестественного?

LT>Смотрим function DaysBetween и видим тот же Trunc.

Упс, извиняюсь, там есть только fuzzy-функции для сравнения. Да и моё предложение про round — тоже не в кассу

А проблему можно решить так: перед вычислением DaysBetween делать "очистку", т.е. выравнивание TDateTime до целых миллисекунд.
Что-нибудь типа такого:
  function PurifyDateTime(dt: TDateTime): TDateTime;
  begin
    result := TimeStampToDateTime(DateTimeToTimeStamp(dt));
  end;



Либо, если предполагается много арифметики со временем — то можно сразу использовать TTimeStamp:
==

TTimeStamp represents time and date values.

Unit
SysUtils

type

TTimeStamp = record
Time: Integer; { Number of milliseconds since midnight }
Date: Integer; { One plus number of days since 1/1/0001 }

end;

Description

Use TTimeStamp to represent date and time values when a great deal of accuracy is required for the time portion. When the time values do not need to be precise to the millisecond, use the more compact TDateTime representation. If additional precision is needed, use TSQLTimeStamp (but do NOT assign it to a Variant).

TTimeStamp is a record with a Time field that represents the number of seconds elapsed since midnight, and a Date field that represents the number of calendar days since the start of the calendar.

(из хелпа Delphi 6)
==
Re[2]: ошибки округления в TDateTime?
От: andy1618 Россия  
Дата: 27.08.08 06:21
Оценка:
S>Ребят, я всё прекрасно понимаю про внутреннюю структуру вещественных чисел, особенности преобразования и т.д. и т.п. Но у меня-то задача конкретная — получить из TDateTime число дней. Способ, предложенный в документации, работает не верно.

Строго говоря, само преобразование в дни работает честно, т.к. в вашем примере до полных суток не хватает парочки каких-нибудь там фемтосекунд

Ну а, чтобы побороть ошибки вещественной арифметики, можно перед преобразованием в дни округлять время до целых секунд/миллисекунд.
Можно так
Автор: andy1618
Дата: 27.08.08
, а можно через RoundTo(t1, -N), где N будет от 6 и выше.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.