похоже вылез очередной баг VCL-ля.
исходная задача: имеем TDateTime, из него нужно получить количество дней. компилятор bcc5.5.1.
логично было бы преобразовать его в int и в нём будет лежать количество дней, но cледующий код выдаёт:
хотелось бы знать, где я ошибся, и что я делаю не так, а то у меня всё большее желание, отказаться от этого самого TDateTime во всех проектах в пользу самописного класса. на сравнениях уже накалывался, теперь вот это
Здравствуйте, svg2003, Вы писали:
S>похоже вылез очередной баг VCL-ля. S>исходная задача: имеем TDateTime, из него нужно получить количество дней. компилятор bcc5.5.1. S>логично было бы преобразовать его в int и в нём будет лежать количество дней, но cледующий код выдаёт:
S>31.12.1899 S>0.9999999999999995559 S>0
S>
S>хотелось бы знать, где я ошибся, и что я делаю не так, а то у меня всё большее желание, отказаться от этого самого TDateTime во всех проектах в пользу самописного класса. на сравнениях уже накалывался, теперь вот это
попробуйте получить число дней встроенными свойствами класса TDateTime
Здравствуйте, 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;
}
Здравствуйте, svg2003, Вы писали:
S>похоже вылез очередной баг VCL-ля.
int(0.9999999999999995559) и должен быть равен 0. Тут как раз всё верно.
Единственный ньюанс это небольшая неточность при конвертации из строки в число: TDateTime t2( "01:00:00" );
Но это вполне допустимо.
Работа с плавающей точкой требует аккуратности.
Например нельзя сравнивать числа с плавающей точкой на равестнство.
Вместо этого нужно сравнивать модуль разности чисел с константой точности.
Для Вашего примера характерна ещё одна неосторожность в работе с плавающей точкой — целочисленное откидывание дробной части (преобразование к инту)
Либо прибавляйте 0.5 к переменной t1 перед преобразованием к int, либо используйте функцию округления (trunc) вместо отбрасывания целой части (int).
H>Либо прибавляйте 0.5 к переменной t1 перед преобразованием к int, либо используйте функцию округления (trunc) вместо отбрасывания целой части (int).
trunc — это, вроде, тоже отбрасывание. Надо round использовать. А вообще, есть такой чудный модуль — DateUtils (по крайней мере, в Delphi) — там разных функций для работы с TDateTime — мама не горюй!
Здравствуйте, andy1618, Вы писали:
A>trunc — это, вроде, тоже отбрасывание. Надо round использовать. А вообще, есть такой чудный модуль — DateUtils (по крайней мере, в Delphi) — там разных функций для работы с TDateTime — мама не горюй!
А чего в оном сверхестественного?
Смотрим function DaysBetween и видим тот же Trunc.
Ребят, я всё прекрасно понимаю про внутреннюю структуру вещественных чисел, особенности преобразования и т.д. и т.п. Но у меня-то задача конкретная — получить из TDateTime число дней. Способ, предложенный в документации, работает не верно. Кстати, +0.5 делать тоже нельзя, потому как 23:00:00 в вещественном виде представлен 0.9583333333333329263. Конечно, можно делать +0.000005 или нечто подобное, что сделал я с пересозданием TDateTime-а, но проблему-то это не решает по сути. Ну нет ошибки в том месте, вылезет потом где-нибудь в другом
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.
S>Ребят, я всё прекрасно понимаю про внутреннюю структуру вещественных чисел, особенности преобразования и т.д. и т.п. Но у меня-то задача конкретная — получить из TDateTime число дней. Способ, предложенный в документации, работает не верно.
Строго говоря, само преобразование в дни работает честно, т.к. в вашем примере до полных суток не хватает парочки каких-нибудь там фемтосекунд
Ну а, чтобы побороть ошибки вещественной арифметики, можно перед преобразованием в дни округлять время до целых секунд/миллисекунд.
Можно так