I want to convert a time_duration to a DATE format, which is the number of days since 1899, 12, 30.
DATE date_from_duration(time_duration td)
{
double days = td.hours()/24.+td.minutes()/(24.*60.)+td.seconds()/(24.*60.*60.);
return days;
}
This code almost works but gives sometimes rounding errors, f.i the time_duration(1007645, 15, 0)
should result in 2014-12-12 00:15:00, but is actually 2014-12-12 00:14:59.
The check of DATE is done with this method, stolen from here:
ptime pTime_from_DATE(double date)
{
using boost::math::modf;
static const ptime::date_type base_date(1899, Dec, 30);
static const ptime base_time(base_date, ptime::time_duration_type(0,0,0));
int dayOffset, hourOffset, minuteOffset, secondOffset;
double fraction = fabs(modf(date, &dayOffset)) * 24; // fraction = hours
fraction = modf(fraction, &hourOffset) * 60; // fraction = minutes
fraction = modf(fraction, &minuteOffset) * 60; // fraction = seconds
modf(fraction, &secondOffset);
ptime t(base_time);
t += ptime::time_duration_type(hourOffset, minuteOffset, secondOffset);
t += ptime::date_duration_type(dayOffset);
return t;
}
Any ideas how to correct this rounding issue efficiently?
That kind of depends on your circumstances.
The general problem is that
1 / (24. * 60. * 60.)
is not exactly representable as a binary float (because 86400 is not a power of two). TheDATE
you get is very nearly exact, but there will be a rounding error. Sometimes this means that it is very little more, sometimes very little less, but there's really not a lot you can do to make it more precise; it is as perfectly alright as you can get. That you see a discrepancy of a second is arguably a problem with your check, in that you stop looking at seconds -- if you check themilliseconds
, you're likely to get 999, making the rounding error look a lot less extreme. This will continue formicroseconds
and possiblynanoseconds
, depending on the resolution oftime_duration
.So quite possibly there's nothing to do because the data is alright. If, however, you don't care about milliseconds and beyond and only want the seconds value to be stable in conversions back and forth, the simplest way to achieve that is to add an epsilon value:
This increases the overall rounding error but ensures that the error is in a "safe" direction, i.e., that converting it back to
time_duration
will give the sameseconds()
value and the visible changes will be at themilliseconds()
level.