[glib] gdatetime: fix floating-point conversion



commit 3cfac71d09dded67485f153fa76d6f063dbb6a76
Author: David Schleef <ds schleef org>
Date:   Sat Apr 13 11:26:34 2013 -0700

    gdatetime: fix floating-point conversion
    
    Conversion from floating point to integer requires special care.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=697715

 glib/gdatetime.c       |   13 ++++++++++++-
 glib/tests/gdatetime.c |   16 ++++++++++++++++
 2 files changed, 28 insertions(+), 1 deletions(-)
---
diff --git a/glib/gdatetime.c b/glib/gdatetime.c
index e1c7260..045c578 100644
--- a/glib/gdatetime.c
+++ b/glib/gdatetime.c
@@ -1295,6 +1295,7 @@ g_date_time_new (GTimeZone *tz,
 {
   GDateTime *datetime;
   gint64 full_time;
+  gint64 usec;
 
   g_return_val_if_fail (tz != NULL, NULL);
 
@@ -1322,10 +1323,20 @@ g_date_time_new (GTimeZone *tz,
                                                 G_TIME_TYPE_STANDARD,
                                                 &full_time);
 
+  /* This is the correct way to convert a scaled FP value to integer.
+   * If this surprises you, please observe that (int)(1.000001 * 1e6)
+   * is 1000000.  This is not a problem with precision, it's just how
+   * FP numbers work.
+   * See https://bugzilla.gnome.org/show_bug.cgi?id=697715. */
+  usec = seconds * USEC_PER_SECOND;
+  if ((usec + 1) * 1e-6 <= seconds) {
+    usec++;
+  }
+
   full_time += UNIX_EPOCH_START * SEC_PER_DAY;
   datetime->days = full_time / SEC_PER_DAY;
   datetime->usec = (full_time % SEC_PER_DAY) * USEC_PER_SECOND;
-  datetime->usec += ((int) (seconds * USEC_PER_SECOND)) % USEC_PER_SECOND;
+  datetime->usec += usec % USEC_PER_SECOND;
 
   return datetime;
 }
diff --git a/glib/tests/gdatetime.c b/glib/tests/gdatetime.c
index f13b9de..03a7935 100644
--- a/glib/tests/gdatetime.c
+++ b/glib/tests/gdatetime.c
@@ -2083,6 +2083,21 @@ test_posix_parse (void)
   g_time_zone_unref (tz);
 }
 
+static void
+test_GDateTime_floating_point (void)
+{
+  GDateTime *dt;
+  GTimeZone *tz;
+
+  g_test_bug ("697715");
+
+  tz = g_time_zone_new ("-03:00");
+  dt = g_date_time_new (tz, 2010, 5, 24,  8, 0, 1.000001);
+  g_time_zone_unref (tz);
+  g_assert_cmpint (g_date_time_get_microsecond (dt), ==, 1);
+  g_date_time_unref (dt);
+}
+
 gint
 main (gint   argc,
       gchar *argv[])
@@ -2140,6 +2155,7 @@ main (gint   argc,
   g_test_add_func ("/GTimeZone/adjust-time", test_adjust_time);
   g_test_add_func ("/GTimeZone/no-header", test_no_header);
   g_test_add_func ("/GTimeZone/posix-parse", test_posix_parse);
+  g_test_add_func ("/GTimeZone/floating-point", test_GDateTime_floating_point);
 
   return g_test_run ();
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]