time::HiRes: don't truncate nanosec utime
authorDavid Mitchell <davem@iabyn.com>
Fri, 27 Apr 2018 11:43:44 +0000 (12:43 +0100)
committerKarl Williamson <khw@cpan.org>
Wed, 8 Aug 2018 17:05:40 +0000 (11:05 -0600)
When passed a floating point atime/mtime value, T::HR::utime()
was converting it into two longs: secs and nsec. But the nanosec value
was calculated using a final NV to long cast, which truncates any
fractional part rather than rounding to nearest. Use a 0.5 addition to
force rounding.

This was manifesting as a test in lib/File/Copy.t failing to preserve
the same mtime after a couple of round trips with utime() and stat().

In particular, the test was attempting to set an mtime to the literal
floating-point value

    1000000000.12345

This value can't be represented exactly as an NV, so was actually
(under -Dquadmath)

1000000000.1234499999999999999999999568211720247320

which was (using truncation) being converted into the two sec/nsec
longs:

    1000000000123449999

After this commit, it instead correctly gets converted to

    1000000000123450000

dist/Time-HiRes/HiRes.xs
dist/Time-HiRes/t/utime.t

index c4a7af7..97e870c 100644 (file)
@@ -1444,10 +1444,16 @@ PROTOTYPE: $$@
                           "): negative time not invented yet",
                               SvNV(accessed), SvNV(modified));
                Zero(&utbuf, sizeof utbuf, char);
+
                utbuf[0].tv_sec = (Time_t)SvNV(accessed);  /* time accessed */
-               utbuf[0].tv_nsec = (long)( ( SvNV(accessed) - utbuf[0].tv_sec ) * 1e9 );
+               utbuf[0].tv_nsec = (long)(
+                        (SvNV(accessed) - (NV)utbuf[0].tv_sec)
+                        * NV_1E9 + (NV)0.5);
+
                utbuf[1].tv_sec = (Time_t)SvNV(modified);  /* time modified */
-               utbuf[1].tv_nsec = (long)( ( SvNV(modified) - utbuf[1].tv_sec ) * 1e9 );
+               utbuf[1].tv_nsec = (long)(
+                        (SvNV(modified) - (NV)utbuf[1].tv_sec)
+                        * NV_1E9 + (NV)0.5);
        }
 
        while (items > 0) {
index 7fd4604..bb4621a 100644 (file)
@@ -112,7 +112,7 @@ BEGIN {
     }
 }
 
-use Test::More tests => 18;
+use Test::More tests => 22;
 BEGIN { push @INC, '.' }
 use t::Watchdog;
 use File::Temp qw( tempfile );
@@ -164,6 +164,19 @@ print "#utime \$filename\n";
        is $got_mtime, $mtime, "mtime set correctly";
 };
 
+print "#utime \$filename round-trip\n";
+{
+       my ($fh, $filename) = tempfile( "Time-HiRes-utime-XXXXXXXXX", UNLINK => 1 );
+        # this fractional part is not exactly representable
+        my $t = 1000000000.12345;
+       is Time::HiRes::utime($t, $t, $filename), 1, "One file changed";
+       my ($got_atime, $got_mtime) = ( Time::HiRes::stat($fh) )[8, 9];
+       is Time::HiRes::utime($got_atime, $got_mtime, $filename), 1, "One file changed";
+       my ($got_atime2, $got_mtime2) = ( Time::HiRes::stat($fh) )[8, 9];
+       is $got_atime, $got_atime2, "atime round trip ok";
+       is $got_mtime, $got_mtime2, "mtime round trip ok";
+};
+
 print "utime \$filename and \$fh\n";
 {
        my ($fh1, $filename1) = tempfile( "Time-HiRes-utime-XXXXXXXXX", UNLINK => 1 );