I ran into this problem with 1% of our records converting views from SQL Server to Oracle where my testing found different results when doing a compare during UAT.
We have a system that needs to express time rounded to tenths of hours because of industry norms in aviation.
In the example below you can see I am finding the difference between two dates, but the result seems to be rounding incorrectly. Normally to test these issues is to try converting the output to a char to see if there is an unseen precision issue.
In this case, I know that Oracle is computing the RAW_DIFF value as .649999999 and is the reason it appears to round incorrectly. What I found surprising is that RAW_CHAR did not return .649999999.
My question is what is the best practice for avoiding this case because I can see how an analyst writing a query would expect ROUND(<val>,1) to just work since this is a 1% kind of issue?
START_DATE 5/7/2025 10:46:00 PM
START_CHAR 2025-05-07 22:46:00
END_DATE 5/7/2025 10:07:00 PM
START_CHAR 2025-05-07 22:07:00
RAW_DIFF 0.65
RAW_CHAR .650000000000 -- actually .649999999 depending on the query tool
SINGLE_ROUND 0.6
DOUBLE_ROUND 0.7
create table ROUND_TEST
(
id NUMBER generated always as identity,
start_date DATE,
end_date DATE
);
insert into round_test
(start_date, end_date)
values
(to_date('5/7/2025 10:46:00 PM','MM/DD/YYYY HH:MI:SS PM'), to_date('5/7/2025 10:07:00 PM','MM/DD/YYYY HH:MI:SS PM'));
select t.start_date,
to_char(t.start_date, 'YYYY-MM-DD HH24:MI:SS') start_char,
t.end_date,
to_char(t.end_date, 'YYYY-MM-DD HH24:MI:SS') end_char,
(t.start_date - t.end_date) * 24 as raw_diff,
to_char((t.start_date - t.end_date) * 24,'9,999.999999999999') as raw_char,
round((t.start_date - t.end_date) * 24, 1) as single_round,
round(round((t.start_date - t.end_date) * 24, 3), 1) as double_round
from round_test t
Add more decimal digits to the format to ensure you're showing the full raw value. A number has a precision of up to 40 digits, so forty 9s should be enough in this case:
select to_char (
(t.start_date - t.end_date) * 24,
'9,999.9999999999999999999999999999999999999999'
) as raw_char
from round_test t;
RAW_CHAR
-----------------------------------------------
.6499999999999999999999999999999999999992
Check your tools to see about setting this as the default to help make this clear.
There are various methods you can use to overcome the problem. Double rounding as you've shown is one.
Another is to cast either or both values to a timestamp. This makes the difference between them an interval. You'll then need to extract out the minutes to convert these to tenths of an hour and add these onto the hours elapsed:
select start_date - cast ( end_date as timestamp ) int_diff,
extract ( minute from start_date - cast ( end_date as timestamp ) ) mins_diff,
round (
extract ( minute from start_date - cast ( end_date as timestamp ) ) / 60, 1
) mins_diff
from round_test;
INT_DIFF MINS_DIFF MINS_DIFF
------------------- ---------- ----------
+00 00:39:00.000000 39 .7
Whatever you method you use, you can reduce the chance of analyst mistakes by baking the formula into the table. Do this by adding a virtual column to the table:
alter table round_test add
hours_duration number(6,1) as (
round (
round ( ( start_date - end_date ) * 24, 3 ),
1
)
);
select * from round_test;
ID START_DATE END_DATE HOURS_DURATION
---------- -------------------- -------------------- --------------
1 07-MAY-2025 22:46:00 07-MAY-2025 22:07:00 .7
This saves everyone having to duplicate the rounding logic.