Hi all,
I have found what I believe to be a minor numerical parsing bug/issue in one of Orekit’s Absolute Date constructors. Specifically the one that takes in a Java.util.Date in it.
I have written a small reproducible test that demonstrates this issue:
public static boolean produceBug(){
String testString = "2019-02-01T13:06:03.115";
TimeScale timeScale=TimeScalesFactory.getUTC();
DateTimeComponents expectedComponent = DateTimeComponents.parseDateTime(testString);
AbsoluteDate expectedDate = new AbsoluteDate(expectedComponent, timeScale);
ZonedDateTime actualComponent = LocalDateTime.from(DateTimeFormatter.ISO_DATE_TIME.parse(testString)).atZone(ZoneOffset.UTC);
AbsoluteDate actualDate = new AbsoluteDate(java.sql.Timestamp.valueOf(actualComponent.toLocalDateTime()),timeScale);
return expectedDate.equals(actualDate);
}
These dates should be identical, they represent the exact same date and time, however the comparison fails. This is because the absolute date’s “offset” is calculated to be subtly different. with the expected epoch being: 0.11500000000000021 (which is within expected double precision) and the actual epoch being: 0.11499999999796273. This precision loss was enough to cause two otherwise identical programs to produce different Chi squared values.
Tracking down the source of this issue lead to the TimeComponents constructor:
public TimeComponents(final int secondInDayA, final double secondInDayB)
throws OrekitIllegalArgumentException {
// split the numbers as a whole number of seconds
// and a fractional part between 0.0 (included) and 1.0 (excluded)
final int carry = (int) FastMath.floor(secondInDayB);
int wholeSeconds = secondInDayA + carry;
final double fractional = secondInDayB - carry;
// range check
if (wholeSeconds < 0 || wholeSeconds > 86400) {
// beware, 86400 must be allowed to cope with leap seconds introduction days
throw new OrekitIllegalArgumentException(OrekitMessages.OUT_OF_RANGE_SECONDS_NUMBER,
wholeSeconds + fractional);
}
// extract the time components
hour = wholeSeconds / 3600;
wholeSeconds -= 3600 * hour;
minute = wholeSeconds / 60;
wholeSeconds -= 60 * minute;
second = wholeSeconds + fractional;
minutesFromUTC = 0;
}
Specifically the line:
final double fractional = secondInDayB - carry;
introduces this minor error. I fixed in my personal situation by replacing this line with:
final double fractional = BigDecimal.valueOf(secondInDayB).subtract(BigDecimal.valueOf(carry)).doubleValue();
however I understand this line introduces additional dependencies and is a bit of a hotfix however this did now return the correct offset value for the constructed AbsoluteDate.
The issue isn’t consistent for every date, so was hard to spot, for example for one date I was parsing the two dates matched completely, but for others this same disparity was introduced.
I am not sure how severe this issue is however it seems like a bad idea to have one constructor return occasionally different results to the rest of them, and the fact this effects tangible results (even if to a very small degree) seems to be a problem.
Thanks for any input/corrections to this problem! It’s taken the better part of two weeks to get to this conclusion