GUI: Find dialog: Compare only to the decimal places of the print format.
authorJohn Darrington <john@darrington.wattle.id.au>
Sun, 14 Jun 2020 15:07:27 +0000 (17:07 +0200)
committerJohn Darrington <john@darrington.wattle.id.au>
Sun, 14 Jun 2020 15:09:15 +0000 (17:09 +0200)
When using the find dialog of the data editor, previously a numeric value
was compared exactly on it's underlying representation (a IEEE754 double
precision float).   This is problematic, because most such floats cannot
be precisely represented in a decimal format, and even if they can be, the
precision required might well exceed that displayed in the data editor.

This change rounds both the reference and the comparand to the number of
decimal places indicated by the variable's print format.

NEWS
src/ui/gui/find-dialog.c

diff --git a/NEWS b/NEWS
index 2322a00f09ade12f2f9254704f4e0cb0a6631366..7cb3c135b43608ce995f7786ebdebc0f8aa228c1 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -6,6 +6,10 @@ Please send PSPP bug reports to bug-gnu-pspp@gnu.org.
 
 Changes from 1.2.0 to 1.3.0:
 
+ * The Find dialog box, when searching for numeric values, will match only
+   to the precision of the variable's print format.  This avoids behaviour
+   which is suprising to some users.
+
  * PSPP now supports the SPSS viewer (.spv) format that SPSS 16 and later
    use to save the contents of its output editor:
 
index 52f26d4a695c64df49a4658d6be6d0b3e64caa37..2ff619a60a25c0c974dcc62092a97a7a40d9f540 100644 (file)
@@ -25,6 +25,7 @@ which match particular strings */
 #include <regex.h>
 #include <stdlib.h>
 #include <sys/types.h>
+#include <math.h>
 
 #include "data/data-in.h"
 #include "data/datasheet.h"
@@ -428,11 +429,13 @@ struct comparator
 };
 
 
-/* A comparator which operates on the unadulterated union values */
-struct value_comparator
+/* A comparator which operates on the numerical values,
+   rounded to the number of decimal places indicated by
+   the variable's format.  */
+struct numeric_comparator
 {
   struct comparator parent;
-  union value pattern;
+  double rounded_ref;
 };
 
 /* A comparator which matches string values or parts thereof */
@@ -454,8 +457,12 @@ static bool
 value_compare (const struct comparator *cmptr,
               const union value *v)
 {
-  const struct value_comparator *vc = (const struct value_comparator *) cmptr;
-  return 0 == value_compare_3way (v, &vc->pattern, var_get_width (cmptr->var));
+  const struct numeric_comparator *nc = (const struct numeric_comparator *) cmptr;
+  const struct fmt_spec *fs = var_get_print_format (cmptr->var);
+
+  double c = nearbyint (v->f * exp10 (fs->d));
+
+  return c == nc->rounded_ref;
 }
 
 
@@ -572,27 +579,21 @@ regexp_destroy (struct comparator *cmptr)
   regfree (&rec->re);
 }
 
-static void
-cmptr_value_destroy (struct comparator *cmptr)
-{
-  struct value_comparator *vc
-    = UP_CAST (cmptr, struct value_comparator, parent);
-  value_destroy (&vc->pattern, var_get_width (cmptr->var));
-}
-
-
 static struct comparator *
-value_comparator_create (const struct variable *var, const char *target)
+numeric_comparator_create (const struct variable *var, const char *target)
 {
-  struct value_comparator *vc = xzalloc (sizeof (*vc));
-  struct comparator *cmptr = &vc->parent;
+  struct numeric_comparator *nc = xzalloc (sizeof (*nc));
+  struct comparator *cmptr = &nc->parent;
 
   cmptr->flags = 0;
   cmptr->var = var;
-  cmptr->compare  = value_compare ;
-  cmptr->destroy = cmptr_value_destroy;
+  cmptr->compare  = value_compare;
+  const struct fmt_spec *fs = var_get_write_format (var);
 
-  text_to_value (target, var, &vc->pattern);
+  union value val;
+  text_to_value (target, var, &val);
+  nc->rounded_ref = nearbyint (val.f * exp10 (fs->d));
+  value_destroy (&val, var_get_width (var));
 
   return cmptr;
 }
@@ -686,7 +687,7 @@ comparator_factory (const struct variable *var, const char *str,
   if (flags & (STR_CMP_SUBSTR | STR_CMP_LABELS))
     return string_comparator_create (var, str, flags);
 
-  return value_comparator_create (var, str);
+  return numeric_comparator_create (var, str);
 }