필터 지우기
필터 지우기

How to preserve nanosecond precision in datetime calculations (for large numbers)

조회 수: 53 (최근 30일)
Hi, I am working with datetime variables with nanosecond precision. I am trying to maintain this precision throughout the calculations, but I lose the precision because the precision of the numbers exceed double precision (for large numbers). For example, 1294871257.002060945 seconds has 3 additional digits more than double precision that I need to preserve in the calculations. I realized that I cannot use the vpa function on a datetime variable. Are there any suggestions on how to handle this? The example script below shows the loss of precision in the calculation by outputing the decimal portions of the original and final numbers.
Thanks,
dt=1294871257.002060945; % nanosecond precision for a large number that exceeds double precision.
epoch = datetime(1980,1,6,'TimeZone','UTCLeapSeconds'); % define the epoch reference time in UTC (and has 0 seconds)
DtUTC = epoch + seconds(dt); % add the duration to the reference time
Time_vec=datevec(DtUTC); % converts final datetime value to a 6-element vector (Y,M,D,H,M,S)
sprintf('%.9f',dt-floor(dt)) % decimal part of original number of seconds
sprintf('%.9f',Time_vec(6)-floor(Time_vec(6))) % decimal part of calculated number of seconds

채택된 답변

Steven Lord
Steven Lord 2021년 11월 11일
Start off with the time as a symbolic object.
dt=sym('1294871257.002060945');
If you don't and just start with dt as a double you've already lost.
eps(double(dt))
ans = 2.3842e-07
fprintf('%0.16f', double(dt))
1294871257.0020608901977539
Now split dt into the integer part and the fractional part symbolically.
frac = dt - floor(dt);
whole = dt - frac;
fprintf('%0.16f', double(frac))
0.0020609450000000
Finally add whole and frac separately to your epoch.
epoch = datetime(1980,1,6,'TimeZone','UTCLeapSeconds');
DtUTC = epoch + seconds(double(whole)) + seconds(double(frac));
DtUTC.Format = 'uuuu-MM-dd''T''HH:mm:ss.SSSSSSSSS''Z'''
DtUTC = datetime
2021-01-16T22:27:19.002060945Z
Time_vec=datevec(DtUTC);
sprintf('%.9f',dt-floor(dt))
ans = '0.002060945'
sprintf('%.9f',Time_vec(6)-floor(Time_vec(6)))
ans = '0.002060945'
  댓글 수: 13
Peter Perkins
Peter Perkins 2021년 11월 30일
Just for the record, this error
The date format for UTCLeapSeconds datetimes must be 'uuuu-MM-dd'T'HH:mm:ss.SSS'Z''
was one of the warts that dpb refers to that was removed in R2021b. Formats for UTCLeapSeconds must still be that ISO form, but may now have 0 to 9 fractiona seconds digits.
dpb
dpb 2021년 11월 30일
Ah! I recall that one, now, Peter. :)

댓글을 달려면 로그인하십시오.

추가 답변 (1개)

dpb
dpb 2021년 11월 11일
>> t='1294871257.002060945'; % treat long value as string
>> dsec=seconds(str2double(extractBefore(t,'.'))) + ...
seconds(str2double(extractAfter(t,strfind(t,'.')-1))); % combine integer/fractional parts
>> dsec.Format='dd:hh:mm:ss.SSSSSSSSS' % format to show nsec resolution
dsec =
duration
14986:22:27:37.002061035
>>
This is the most straightforward workaround I can think of within the limittions of the (somewhat hampered) durations class which has limited input options for formatting input and the helper functions such as seconds that are only base numeric classes aware.
The datetime class is still pretty young; it has much left to be worked out in order to make it fully functional over niche usage such as yours.
You'll probably have to build a wrapper class of functions to hide all the internal machinations required; the above just illustrates that if you can break the whole number of seconds precision required into pieces that are each within the precision of a double that the duration object itself can deal with them to that precision -- at least storing an input number. I've not tested about rounding when try to do arithmetic with the result.
  댓글 수: 5
Matthew Casiano
Matthew Casiano 2021년 11월 12일
Also, I have noticed that in the symbolic approach, there is a nuance. It must be a symbolic variable, not a symbolic number. In other words:
This works,
dt=sym('1294871257.002060945');
This does not work,
dt=sym(1294871257.002060945);
I believe that as a symbolic variable, it is the only way to maintain the needed accuracy. But I am not clear on the nuance here.
Steven Lord
Steven Lord 2021년 11월 12일
This:
dt=sym(1294871257.002060945);
evaluates 1294871257.002060945 in double precision and converts the resulting double into symbolic. As I stated in my answer the first of those steps has already caused problems for your approach.
format longg
dt = 1294871257.002060945
dt =
1294871257.00206
% Spacing between dt and the next largest *representable* double
eps(dt)
ans =
2.38418579101562e-07
% This is the closest representable double to the number you entered
fprintf('%0.16f', dt)
1294871257.0020608901977539
This:
dt=sym('1294871257.002060945');
vpa(dt, 20)
ans = 
1294871257.002060945
doesn't go through double at all.
In some cases sym can "recognize" the floating-point result of an operation that should be of a particular form and compensate for roundoff error. See the description of the flag input argument to the sym function, specifically the row dealing with the (default) 'r' flag, for a list of recognized expressions. So as an example even though 1/3 is not exactly one third, it's close enough for sym. But your number doesn't fall into one of those recognized expression categories.
x = sym(1/3)
x = 

댓글을 달려면 로그인하십시오.

카테고리

Help CenterFile Exchange에서 Linear Algebra에 대해 자세히 알아보기

제품


릴리스

R2021a

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by