Redo VFM interface. Get rid of compaction_necessary, compaction_nval,
[pspp-builds.git] / src / crosstabs.q
index 88c7349120f6a2b0a17fbc27ac4f219df2b5e63c..97db9763d76adfc5df9c8e65cd6e2d7fd006311d 100644 (file)
 
 */
 
-/* AIX requires this to be the first thing in the file.  */
 #include <config.h>
-#if __GNUC__
-#define alloca __builtin_alloca
-#else
-#if HAVE_ALLOCA_H
-#include <alloca.h>
-#else
-#ifdef _AIX
-#pragma alloca
-#else
-#ifndef alloca                 /* predefined by HP cc +Olibcalls */
-char *alloca ();
-#endif
-#endif
-#endif
-#endif
-
 #include <assert.h>
 #include <ctype.h>
 #include <stdlib.h>
@@ -55,7 +38,6 @@ char *alloca ();
 #include "alloc.h"
 #include "hash.h"
 #include "pool.h"
-#include "dcdflib/cdflib.h"
 #include "command.h"
 #include "lexer.h"
 #include "error.h"
@@ -109,7 +91,7 @@ struct table_entry
        double *data;   /* Crosstabulation table for integer mode. */
       }
     u;
-    union value v[1];          /* Values. */
+    union value values[1];     /* Values. */
   };
 
 /* A crosstabulation. */
@@ -118,7 +100,7 @@ struct crosstab
     int nvar;                  /* Number of variables. */
     double missing;            /* Missing cases count. */
     int ofs;                   /* Integer mode: Offset into sorted_tab[]. */
-    struct variable *v[2];     /* At least two variables; sorted by
+    struct variable *vars[2];  /* At least two variables; sorted by
                                   larger indices first. */
   };
 
@@ -134,8 +116,9 @@ static struct hsh_table *gen_tab;   /* Hash table. */
 static int n_sorted_tab;               /* Number of entries in sorted_tab. */
 static struct table_entry **sorted_tab;        /* Sorted table. */
 
-/* VARIABLES dictionary. */
-static struct dictionary *var_dict;
+/* Variables specifies on VARIABLES. */
+static struct variable **variables;
+static size_t variables_cnt;
 
 /* TABLES. */
 static struct crosstab **xtab;
@@ -152,7 +135,6 @@ static int mode;
 /* CELLS. */
 static int num_cells;          /* Number of cells requested. */
 static int cells[8];           /* Cells requested. */
-static int expected;           /* Nonzero if expected value is needed. */
 
 /* WRITE. */
 static int write;              /* One of WR_* that specifies the WRITE style. */
@@ -165,13 +147,15 @@ static struct pool *pl_tc;        /* For table cells. */
 static struct pool *pl_col;    /* For column data. */
 
 static int internal_cmd_crosstabs (void);
-static void free_var_dict (void);
-static void precalc (void);
-static int calc_general (struct ccase *);
-static int calc_integer (struct ccase *);
-static void postcalc (void);
+static void precalc (void *);
+static int calc_general (struct ccase *, void *);
+static int calc_integer (struct ccase *, void *);
+static void postcalc (void *);
 static void submit (struct tab_table *);
 
+static void format_short (char *s, const struct fmt_spec *fp,
+                         const union value *v);
+
 #if DEBUGGING
 static void debug_print (void);
 static void print_table_entries (struct table_entry **tab);
@@ -183,7 +167,7 @@ cmd_crosstabs (void)
 {
   int result = internal_cmd_crosstabs ();
 
-  free_var_dict ();
+  free (variables);
   pool_destroy (pl_tc);
   pool_destroy (pl_col);
   
@@ -194,7 +178,8 @@ cmd_crosstabs (void)
 static int
 internal_cmd_crosstabs (void)
 {
-  var_dict = NULL;
+  variables = NULL;
+  variables_cnt = 0;
   xtab = NULL;
   nxtab = 0;
   pl_tc = pool_create ();
@@ -205,15 +190,13 @@ internal_cmd_crosstabs (void)
     return CMD_FAILURE;
 
 #if DEBUGGING
-  /* Needs var_dict. */
+  /* Needs variables. */
   debug_print ();
 #endif
 
-  mode = var_dict ? INTEGER : GENERAL;
-  free_var_dict();
+  mode = variables ? INTEGER : GENERAL;
 
   /* CELLS. */
-  expected = 0;
   if (!cmd.sbc_cells)
     {
       cmd.a_cells[CRS_CL_COUNT] = 1;
@@ -243,11 +226,7 @@ internal_cmd_crosstabs (void)
       cmd.a_cells[CRS_CL_NONE] = 0;
       for (num_cells = i = 0; i < CRS_CL_count; i++)
        if (cmd.a_cells[i])
-         {
-           if (i >= CRS_CL_EXPECTED)
-             expected = 1;
-           cmd.a_cells[num_cells++] = i;
-         }
+          cmd.a_cells[num_cells++] = i;
     }
 
   /* STATISTICS. */
@@ -295,45 +274,18 @@ internal_cmd_crosstabs (void)
   else
     write = CRS_WR_NONE;
 
-  update_weighting (&default_dict);
-  procedure (precalc, mode == GENERAL ? calc_general : calc_integer, postcalc);
+  procedure_with_splits (precalc,
+                         mode == GENERAL ? calc_general : calc_integer,
+                         postcalc, NULL);
 
   return CMD_SUCCESS;
 }
 
-/* Frees var_dict once it's no longer needed. */
-static void
-free_var_dict (void)
-{
-  if (!var_dict)
-    return;
-  
-  {
-    int i;
-
-    if (var_dict->name_tab)
-      {
-       hsh_destroy (var_dict->name_tab);
-       var_dict->name_tab = NULL;
-      }
-
-    for (i = 0; i < var_dict->nvar; i++)
-      free (var_dict->var[i]);
-    free (var_dict->var);
-    var_dict->var = NULL;
-    var_dict->nvar = 0;
-
-    free_dictionary (var_dict);
-
-    var_dict = NULL;
-  }
-}
-
 /* Parses the TABLES subcommand. */
 static int
-crs_custom_tables (struct cmd_crosstabs *cmd unused)
+crs_custom_tables (struct cmd_crosstabs *cmd UNUSED)
 {
-  struct dictionary *dict;
+  struct var_set *var_set;
   int n_by;
   struct variable ***by = NULL;
   int *by_nvar = NULL;
@@ -342,20 +294,24 @@ crs_custom_tables (struct cmd_crosstabs *cmd unused)
 
   /* Ensure that this is a TABLES subcommand. */
   if (!lex_match_id ("TABLES")
-      && (token != T_ID || !is_varname (tokid))
+      && (token != T_ID || dict_lookup_var (default_dict, tokid) == NULL)
       && token != T_ALL)
     return 2;
   lex_match ('=');
 
-  dict = var_dict ? var_dict : &default_dict;
+  if (variables != NULL)
+    var_set = var_set_create_from_array (variables, variables_cnt);
+  else
+    var_set = var_set_create_from_dict (default_dict);
+  assert (var_set != NULL);
   
   for (n_by = 0; ;)
     {
       by = xrealloc (by, sizeof *by * (n_by + 1));
       by_nvar = xrealloc (by_nvar, sizeof *by_nvar * (n_by + 1));
-      if (!parse_variables (dict, &by[n_by], &by_nvar[n_by],
-                           PV_NO_DUPLICATE | PV_NO_SCRATCH))
-       goto lossage;
+      if (!parse_var_set_vars (var_set, &by[n_by], &by_nvar[n_by],
+                               PV_NO_DUPLICATE | PV_NO_SCRATCH))
+       goto done;
       nx *= by_nvar[n_by];
       n_by++;
 
@@ -364,7 +320,7 @@ crs_custom_tables (struct cmd_crosstabs *cmd unused)
          if (n_by < 1)
            {
              lex_error (_("expecting BY"));
-             goto lossage;
+             goto done;
            }
          else 
            break;
@@ -387,12 +343,8 @@ crs_custom_tables (struct cmd_crosstabs *cmd unused)
        {
          int i;
 
-         if (var_dict == NULL)
-           for (i = 0; i < n_by; i++)
-             x->v[i] = by[i][by_iter[i]];
-         else
-           for (i = 0; i < n_by; i++)
-             x->v[i] = default_dict.var[by[i][by_iter[i]]->foo];
+          for (i = 0; i < n_by; i++)
+            x->vars[i] = by[i][by_iter[i]];
        }
        
        {
@@ -410,11 +362,10 @@ crs_custom_tables (struct cmd_crosstabs *cmd unused)
       }
     free (by_iter);
   }
-    
   success = 1;
-  /* Despite the name, we come here whether we're successful or
-     not. */
- lossage:
+
+ done:
+  /* All return paths lead here. */
   {
     int i;
 
@@ -424,16 +375,15 @@ crs_custom_tables (struct cmd_crosstabs *cmd unused)
     free (by_nvar);
   }
 
+  var_set_destroy (var_set);
+
   return success;
 }
 
 /* Parses the VARIABLES subcommand. */
 static int
-crs_custom_variables (struct cmd_crosstabs *cmd unused)
+crs_custom_variables (struct cmd_crosstabs *cmd UNUSED)
 {
-  struct variable **v = NULL;
-  int nv = 0;
-
   if (nxtab)
     {
       msg (SE, _("VARIABLES must be specified before TABLES."));
@@ -444,12 +394,12 @@ crs_custom_variables (struct cmd_crosstabs *cmd unused)
   
   for (;;)
     {
-      int orig_nv = nv;
+      int orig_nv = variables_cnt;
       int i;
 
       long min, max;
       
-      if (!parse_variables (&default_dict, &v, &nv,
+      if (!parse_variables (default_dict, &variables, &variables_cnt,
                            (PV_APPEND | PV_NUMERIC
                             | PV_NO_DUPLICATE | PV_NO_SCRATCH)))
        return 0;
@@ -486,40 +436,22 @@ crs_custom_variables (struct cmd_crosstabs *cmd unused)
        }
       lex_get ();
       
-      for (i = orig_nv; i < nv; i++)
+      for (i = orig_nv; i < variables_cnt; i++)
        {
-         v[i]->p.crs.min = min;
-         v[i]->p.crs.max = max + 1.;
-         v[i]->p.crs.count = max - min + 1;
+         variables[i]->p.crs.min = min;
+         variables[i]->p.crs.max = max + 1.;
+         variables[i]->p.crs.count = max - min + 1;
        }
       
       if (token == '/')
        break;
     }
   
-  {
-    int i;
-    
-    var_dict = new_dictionary (0);
-    var_dict->var = xmalloc (sizeof *var_dict->var * nv);
-    var_dict->nvar = nv;
-    for (i = 0; i < nv; i++)
-      {
-       struct variable *var = xmalloc (offsetof (struct variable, width));
-       strcpy (var->name, v[i]->name);
-       var->index = i;
-       var->type = v[i]->type;
-       var->foo = v[i]->index;
-       var_dict->var[i] = var;
-       hsh_force_insert (var_dict->name_tab, var);
-      }
-
-    free (v);
-    return 1;
-  }
+  return 1;
 
  lossage:
-  free (v);
+  free (variables);
+  variables = NULL;
   return 0;
 }
 
@@ -529,27 +461,25 @@ debug_print (void)
 {
   printf ("CROSSTABS\n");
 
-  if (var_dict)
+  if (variables != NULL)
     {
       int i;
 
       printf ("\t/VARIABLES=");
-      for (i = 0; i < var_dict->nvar; i++)
+      for (i = 0; i < variables_cnt; i++)
        {
-         struct variable *v = var_dict->var[i];
-         struct variable *iv = default_dict.var[v->foo];
+         struct variable *v = variables[i];
 
          printf ("%s ", v->name);
-         if (i < var_dict->nvar - 1)
+         if (i < variables_cnt - 1)
            {
-             struct variable *nv = var_dict->var[i + 1];
-             struct variable *niv = default_dict.var[nv->foo];
+             struct variable *nv = variables[i + 1];
              
-             if (iv->p.crs.min == niv->p.crs.min
-                 && iv->p.crs.max == niv->p.crs.max)
+             if (v->p.crs.min == nv->p.crs.min
+                 && v->p.crs.max == nv->p.crs.max)
                continue;
            }
-         printf ("(%d,%d) ", iv->p.crs.min, iv->p.crs.max - 1);
+         printf ("(%d,%d) ", v->p.crs.min, v->p.crs.max - 1);
        }
       printf ("\n");
     }
@@ -584,7 +514,7 @@ static unsigned hash_table_entry (const void *, void *);
 
 /* Set up the crosstabulation tables for processing. */
 static void
-precalc (void)
+precalc (void *aux UNUSED)
 {
   if (mode == GENERAL)
     {
@@ -608,13 +538,13 @@ precalc (void)
          x->ofs = n_sorted_tab;
 
          for (j = 2; j < x->nvar; j++)
-           count *= x->v[j - 2]->p.crs.count;
+           count *= x->vars[j - 2]->p.crs.count;
 
          sorted_tab = xrealloc (sorted_tab,
                                 sizeof *sorted_tab * (n_sorted_tab + count));
          v = local_alloc (sizeof *v * x->nvar);
          for (j = 2; j < x->nvar; j++)
-           v[j] = x->v[j]->p.crs.min;
+           v[j] = x->vars[j]->p.crs.min;
          for (j = 0; j < count; j++)
            {
              struct table_entry *te;
@@ -625,8 +555,8 @@ precalc (void)
              te->table = i;
              
              {
-               const int mat_size = (x->v[0]->p.crs.count
-                                     * x->v[1]->p.crs.count);
+               const int mat_size = (x->vars[0]->p.crs.count
+                                     * x->vars[1]->p.crs.count);
                int m;
                
                te->u.data = xmalloc (sizeof *te->u.data * mat_size);
@@ -635,10 +565,10 @@ precalc (void)
              }
              
              for (k = 2; k < x->nvar; k++)
-               te->v[k].f = v[k];
+               te->values[k].f = v[k];
              for (k = 2; k < x->nvar; k++)
-               if (++v[k] >= x->v[k]->p.crs.max)
-                 v[k] = x->v[k]->p.crs.min;
+               if (++v[k] >= x->vars[k]->p.crs.max)
+                 v[k] = x->vars[k]->p.crs.min;
                else
                  break;
            }
@@ -653,12 +583,10 @@ precalc (void)
 
 /* Form crosstabulations for general mode. */
 static int
-calc_general (struct ccase *c)
+calc_general (struct ccase *c, void *aux UNUSED)
 {
   /* Case weight. */
-  double w = (default_dict.weight_index != -1
-             ? c->data[default_dict.var[default_dict.weight_index]->fv].f
-             : 1.0);
+  double weight = dict_get_case_weight (default_dict, c);
 
   /* Flattened current table index. */
   int t;
@@ -679,23 +607,25 @@ calc_general (struct ccase *c)
        for (j = 0; j < x->nvar; j++)
          {
            if ((cmd.miss == CRS_TABLE
-                && is_missing (&c->data[x->v[j]->fv], x->v[j]))
+                && is_missing (&c->data[x->vars[j]->fv], x->vars[j]))
                || (cmd.miss == CRS_INCLUDE
-                   && is_system_missing (&c->data[x->v[j]->fv], x->v[j])))
+                   && is_system_missing (&c->data[x->vars[j]->fv],
+                                          x->vars[j])))
              {
-               x->missing += w;
+               x->missing += weight;
                goto next_crosstab;
              }
              
-           if (x->v[j]->type == NUMERIC)
-             te->v[j].f = c->data[x->v[j]->fv].f;
+           if (x->vars[j]->type == NUMERIC)
+             te->values[j].f = c->data[x->vars[j]->fv].f;
            else
              {
-               memcpy (te->v[j].s, c->data[x->v[j]->fv].s, x->v[j]->width);
+               memcpy (te->values[j].s, c->data[x->vars[j]->fv].s,
+                        x->vars[j]->width);
              
                /* Necessary in order to simplify comparisons. */
-               memset (&te->v[j].s[x->v[j]->width], 0,
-                       sizeof (union value) - x->v[j]->width);
+               memset (&te->values[j].s[x->vars[j]->width], 0,
+                       sizeof (union value) - x->vars[j]->width);
              }
          }
       }
@@ -708,13 +638,13 @@ calc_general (struct ccase *c)
          {
            struct table_entry *tep = pool_alloc (pl_tc, entry_size);
            
-           te->u.freq = w;
+           te->u.freq = weight;
            memcpy (tep, te, entry_size);
            
            *tepp = tep;
          }
        else
-         (*tepp)->u.freq += w;
+         (*tepp)->u.freq += weight;
       }
 
     next_crosstab:
@@ -725,12 +655,10 @@ calc_general (struct ccase *c)
 }
 
 static int
-calc_integer (struct ccase *c)
+calc_integer (struct ccase *c, void *aux UNUSED)
 {
   /* Case weight. */
-  double w = (default_dict.weight_index != -1
-             ? c->data[default_dict.var[default_dict.weight_index]->fv].f
-             : 1.0);
+  double weight = dict_get_case_weight (default_dict, c);
   
   /* Flattened current table index. */
   int t;
@@ -744,14 +672,14 @@ calc_integer (struct ccase *c)
       ofs = x->ofs;
       for (i = 0; i < x->nvar; i++)
        {
-         struct variable *const v = x->v[i];
+         struct variable *const v = x->vars[i];
          double value = c->data[v->fv].f;
          
          /* Note that the first test also rules out SYSMIS. */
          if ((value < v->p.crs.min || value >= v->p.crs.max)
              || (cmd.miss == CRS_TABLE && is_num_user_missing (value, v)))
            {
-             x->missing += w;
+             x->missing += weight;
              goto next_crosstab;
            }
          
@@ -763,11 +691,11 @@ calc_integer (struct ccase *c)
        }
       
       {
-       const int row = c->data[x->v[ROW_VAR]->fv].f - x->v[ROW_VAR]->p.crs.min;
-       const int col = c->data[x->v[COL_VAR]->fv].f - x->v[COL_VAR]->p.crs.min;
-       const int col_dim = x->v[COL_VAR]->p.crs.count;
+       const int row = c->data[x->vars[ROW_VAR]->fv].f - x->vars[ROW_VAR]->p.crs.min;
+       const int col = c->data[x->vars[COL_VAR]->fv].f - x->vars[COL_VAR]->p.crs.min;
+       const int col_dim = x->vars[COL_VAR]->p.crs.count;
 
-       sorted_tab[ofs]->u.data[col + row * col_dim] += w;
+       sorted_tab[ofs]->u.data[col + row * col_dim] += weight;
       }
       
     next_crosstab: ;
@@ -809,7 +737,7 @@ print_table_entries (struct table_entry **tab)
 /* Compare the table_entry's at A and B and return a strcmp()-type
    result. */
 static int 
-compare_table_entry (const void *a_, const void *b_, void *foo unused)
+compare_table_entry (const void *a_, const void *b_, void *foo UNUSED)
 {
   const struct table_entry *a = a_;
   const struct table_entry *b = b_;
@@ -824,9 +752,9 @@ compare_table_entry (const void *a_, const void *b_, void *foo unused)
     int i;
 
     for (i = x->nvar - 1; i >= 0; i--)
-      if (x->v[i]->type == NUMERIC)
+      if (x->vars[i]->type == NUMERIC)
        {
-         const double diffnum = a->v[i].f - b->v[i].f;
+         const double diffnum = a->values[i].f - b->values[i].f;
          if (diffnum < 0)
            return -1;
          else if (diffnum > 0)
@@ -834,9 +762,10 @@ compare_table_entry (const void *a_, const void *b_, void *foo unused)
        }
       else 
        {
-         assert (x->v[i]->type == ALPHA);
+         assert (x->vars[i]->type == ALPHA);
          {
-           const int diffstr = strncmp (a->v[i].s, b->v[i].s, x->v[i]->width);
+           const int diffstr = strncmp (a->values[i].s, b->values[i].s,
+                                         x->vars[i]->width);
            if (diffstr)
              return diffstr;
          }
@@ -846,23 +775,17 @@ compare_table_entry (const void *a_, const void *b_, void *foo unused)
   return 0;
 }
 
-/* Calculate a hash value from table_entry PA. */
+/* Calculate a hash value from table_entry A. */
 static unsigned
-hash_table_entry (const void *pa, void *foo unused)
+hash_table_entry (const void *a_, void *foo UNUSED)
 {
-  const struct table_entry *a = pa;
-  unsigned long hash = a->table;
+  const struct table_entry *a = a_;
+  unsigned long hash;
   int i;
 
-  /* Hash formula from _SPSS Statistical Algorithms_. */
+  hash = a->table;
   for (i = 0; i < xtab[a->table]->nvar; i++)
-    {
-      hash = (hash << 3) | (hash >> (CHAR_BIT * SIZEOF_LONG - 3));
-      hash ^= a->v[i].hash[0];
-#if SIZEOF_DOUBLE / SIZEOF_LONG > 1
-      hash ^= a->v[i].hash[1];
-#endif
-    }
+    hash ^= hsh_hash_bytes (&a->values[i], sizeof a->values[i]);
   
   return hash;
 }
@@ -880,7 +803,7 @@ static void output_pivot_table (struct table_entry **, struct table_entry **,
 static void make_summary_table (void);
 
 static void
-postcalc (void)
+postcalc (void *aux UNUSED)
 {
   if (mode == GENERAL)
     {
@@ -972,8 +895,8 @@ make_summary_table (void)
       else
        {
          const struct crosstab *const x = xtab[(*pb)->table];
-         const int n_cols = x->v[COL_VAR]->p.crs.count;
-         const int n_rows = x->v[ROW_VAR]->p.crs.count;
+         const int n_cols = x->vars[COL_VAR]->p.crs.count;
+         const int n_rows = x->vars[ROW_VAR]->p.crs.count;
          const int count = n_cols * n_rows;
            
          for (valid = 0.; pb < pe; pb++)
@@ -1016,7 +939,8 @@ insert_summary (struct tab_table *t, int tab_index, double valid)
        if (i > 0)
          cp = stpcpy (cp, " * ");
 
-       cp = stpcpy (cp, x->v[i]->label ? x->v[i]->label : x->v[i]->name);
+       cp = stpcpy (cp,
+                     x->vars[i]->label ? x->vars[i]->label : x->vars[i]->name);
       }
     tab_text (t, 0, 0, TAB_LEFT, buf);
 
@@ -1107,7 +1031,7 @@ output_pivot_table (struct table_entry **pb, struct table_entry **pe,
   int tc = pe - pb;            /* Table count. */
 
   /* Table entry for header comparison. */
-  struct table_entry *cmp;
+  struct table_entry *cmp = NULL;
 
   x = xtab[(*pb)->table];
   enum_var_values (pb, pe - pb, COL_VAR, &cols, &n_cols);
@@ -1123,7 +1047,7 @@ output_pivot_table (struct table_entry **pb, struct table_entry **pe,
 
       /* First header line. */
       tab_joint_text (table, nvar - 1, 0, (nvar - 1) + (n_cols - 1), 0,
-                     TAB_CENTER | TAT_TITLE, x->v[COL_VAR]->name);
+                     TAB_CENTER | TAT_TITLE, x->vars[COL_VAR]->name);
   
       tab_hline (table, TAL_1, nvar - 1, nvar + n_cols - 2, 1);
             
@@ -1134,12 +1058,13 @@ output_pivot_table (struct table_entry **pb, struct table_entry **pe,
        for (i = 2; i < nvar; i++)
          tab_joint_text (table, nvar - i - 1, 0, nvar - i - 1, 1,
                          TAB_RIGHT | TAT_TITLE,
-                         x->v[i]->label ? x->v[i]->label : x->v[i]->name);
+                         (x->vars[i]->label
+                           ? x->vars[i]->label : x->vars[i]->name));
        tab_text (table, nvar - 2, 1, TAB_RIGHT | TAT_TITLE,
-                 x->v[ROW_VAR]->name);
+                 x->vars[ROW_VAR]->name);
        for (i = 0; i < n_cols; i++)
          table_value_missing (table, nvar + i - 1, 1, TAB_RIGHT, &cols[i],
-                              x->v[COL_VAR]);
+                              x->vars[COL_VAR]);
        tab_text (table, nvar + n_cols - 1, 1, TAB_CENTER, _("Total"));
       }
 
@@ -1157,11 +1082,12 @@ output_pivot_table (struct table_entry **pb, struct table_entry **pe,
            {
              if (i)
                cp = stpcpy (cp, " by ");
-             cp = stpcpy (cp, x->v[i]->name);
+             cp = stpcpy (cp, x->vars[i]->name);
            }
        else
          {
-           cp = spprintf (cp, "%s by %s for", x->v[0]->name, x->v[1]->name);
+           cp = spprintf (cp, "%s by %s for",
+                           x->vars[0]->name, x->vars[1]->name);
            for (i = 2; i < nvar; i++)
              {
                char buf[64], *bufp;
@@ -1169,9 +1095,9 @@ output_pivot_table (struct table_entry **pb, struct table_entry **pe,
                if (i > 2)
                  *cp++ = ',';
                *cp++ = ' ';
-               cp = stpcpy (cp, x->v[i]->name);
+               cp = stpcpy (cp, x->vars[i]->name);
                *cp++ = '=';
-               data_out (buf, &x->v[i]->print, &(*pb)->v[i]);
+               format_short (buf, &x->vars[i]->print, &(*pb)->values[i]);
                for (bufp = buf; isspace ((unsigned char) *bufp); bufp++)
                  ;
                cp = stpcpy (cp, bufp);
@@ -1360,7 +1286,7 @@ output_pivot_table (struct table_entry **pb, struct table_entry **pe,
            *cp = 0.;
            for (p = &tb[0]; p < te; p++)
              {
-               for (; memcmp (cur_col, &(*p)->v[COL_VAR], sizeof *cur_col);
+               for (; memcmp (cur_col, &(*p)->values[COL_VAR], sizeof *cur_col);
                     cur_row = rows)
                  {
                    *++cp = 0.;
@@ -1373,7 +1299,7 @@ output_pivot_table (struct table_entry **pb, struct table_entry **pe,
                    mp = &mat[cur_col - cols];
                  }
 
-               for (; memcmp (cur_row, &(*p)->v[ROW_VAR], sizeof *cur_row);
+               for (; memcmp (cur_row, &(*p)->values[ROW_VAR], sizeof *cur_row);
                     cur_row++)
                  {
                    *mp = 0.;
@@ -1508,8 +1434,9 @@ output_pivot_table (struct table_entry **pb, struct table_entry **pe,
          for (; ; first_difference--)
            {
              assert (first_difference >= 2);
-             if (memcmp (&cmp->v[first_difference],
-                         &(*tb)->v[first_difference], sizeof *cmp->v))
+             if (memcmp (&cmp->values[first_difference],
+                         &(*tb)->values[first_difference],
+                          sizeof *cmp->values))
                break;
            }
        cmp = *tb;
@@ -1568,7 +1495,7 @@ delete_missing (void)
     int r;
 
     for (r = 0; r < n_rows; r++)
-      if (is_num_user_missing (rows[r].f, x->v[ROW_VAR]))
+      if (is_num_user_missing (rows[r].f, x->vars[ROW_VAR]))
        {
          int c;
 
@@ -1582,7 +1509,7 @@ delete_missing (void)
     int c;
 
     for (c = 0; c < n_cols; c++)
-      if (is_num_user_missing (cols[c].f, x->v[COL_VAR]))
+      if (is_num_user_missing (cols[c].f, x->vars[COL_VAR]))
        {
          int r;
 
@@ -1612,7 +1539,7 @@ submit (struct tab_table *t)
   if (t != table)
     for (i = 2; i < nvar; i++)
       tab_text (t, nvar - i - 1, 0, TAB_RIGHT | TAT_TITLE,
-               x->v[i]->label ? x->v[i]->label : x->v[i]->name);
+               x->vars[i]->label ? x->vars[i]->label : x->vars[i]->name);
   tab_box (t, TAL_2, TAL_2, -1, -1, 0, 0, tab_nc (t) - 1, tab_nr (t) - 1);
   tab_box (t, -1, -1, -1, TAL_1, tab_l (t), tab_t (t) - 1, tab_nc (t) - 1,
           tab_nr (t) - 1);
@@ -1701,7 +1628,7 @@ find_pivot_extent_general (struct table_entry **tp, int *cnt, int pivot)
       if (pivot)
        continue;
 
-      if (memcmp (&(*tp)->v[2], &fp->v[2], sizeof (union value) * (x->nvar - 2)))
+      if (memcmp (&(*tp)->values[2], &fp->values[2], sizeof (union value) * (x->nvar - 2)))
        break;
     }
 
@@ -1735,7 +1662,8 @@ find_pivot_extent_integer (struct table_entry **tp, int *cnt, int pivot)
       if (pivot)
        continue;
       
-      if (memcmp (&(*tp)->v[2], &fp->v[2], sizeof (union value) * (x->nvar - 2)))
+      if (memcmp (&(*tp)->values[2], &fp->values[2],
+                  sizeof (union value) * (x->nvar - 2)))
        break;
     }
 
@@ -1771,18 +1699,19 @@ enum_var_values (struct table_entry **entries, int entry_cnt, int var_idx,
 {
   if (mode == GENERAL)
     {
-      int width = xtab[(*entries)->table]->v[var_idx]->width;
+      int width = xtab[(*entries)->table]->vars[var_idx]->width;
       int i;
 
       *values = xmalloc (sizeof **values * entry_cnt);
       for (i = 0; i < entry_cnt; i++)
-        (*values)[i] = entries[i]->v[var_idx];
+        (*values)[i] = entries[i]->values[var_idx];
       *value_cnt = sort_unique (*values, entry_cnt, sizeof **values,
                                 compare_value, &width);
     }
   else
     {
-      struct crosstab_proc *crs = &xtab[(*entries)->table]->v[var_idx]->p.crs;
+      struct crosstab_proc *crs
+        = &xtab[(*entries)->table]->vars[var_idx]->p.crs;
       int i;
       
       assert (mode == INTEGER);
@@ -1809,9 +1738,9 @@ table_value_missing (struct tab_table *table, int c, int r, unsigned char opt,
       return;
     }
 
-  s.length = var->print.w;
-  s.string = tab_alloc (table, s.length + 1);
-  data_out (s.string, &var->print, v);
+  s.string = tab_alloc (table, var->print.w);
+  format_short (s.string, &var->print, v);
+  s.length = strlen (s.string);
   if (cmd.miss == CRS_REPORT && is_num_user_missing (v->f, var))
     s.string[s.length++] = 'M';
   while (s.length && *s.string == ' ')
@@ -1834,8 +1763,8 @@ display_dimensions (struct tab_table *table, int first_difference, struct table_
 
   for (; first_difference >= 2; first_difference--)
     table_value_missing (table, nvar - first_difference - 1, 0,
-                        TAB_RIGHT, &tb->v[first_difference],
-                        x->v[first_difference]);
+                        TAB_RIGHT, &tb->values[first_difference],
+                        x->vars[first_difference]);
 }
 
 /* Put value V into cell (C,R) of TABLE, suffixed with letter M. */
@@ -1848,7 +1777,7 @@ float_M_suffix (struct tab_table *table, int c, int r, double v)
   s.length = 9;
   s.string = tab_alloc (table, 9);
   s.string[8] = 'M';
-  data_out (s.string, &f, (union value *) &v);
+  format_short (s.string, &f, (union value *) &v);
   while (*s.string == ' ')
     {
       s.length--;
@@ -1866,7 +1795,7 @@ display_crosstabulation (void)
        
     for (r = 0; r < n_rows; r++)
       table_value_missing (table, nvar - 2, r * num_cells,
-                          TAB_RIGHT, &rows[r], x->v[ROW_VAR]);
+                          TAB_RIGHT, &rows[r], x->vars[ROW_VAR]);
   }
   tab_text (table, nvar - 2, n_rows * num_cells,
            TAB_LEFT, _("Total"));
@@ -1883,10 +1812,7 @@ display_crosstabulation (void)
          tab_hline (table, TAL_1, -1, n_cols, 0);
        for (c = 0; c < n_cols; c++)
          {
-           double expected_value;
-
-           if (expected)
-             expected_value = row_tot[r] * col_tot[c] / W;
+           double expected_value = row_tot[r] * col_tot[c] / W;
            for (i = 0; i < num_cells; i++)
              {
                double v;
@@ -1925,8 +1851,8 @@ display_crosstabulation (void)
                  }
 
                if (cmd.miss == CRS_REPORT
-                   && (is_num_user_missing (cols[c].f, x->v[COL_VAR])
-                       || is_num_user_missing (rows[r].f, x->v[ROW_VAR])))
+                   && (is_num_user_missing (cols[c].f, x->vars[COL_VAR])
+                       || is_num_user_missing (rows[r].f, x->vars[ROW_VAR])))
                  float_M_suffix (table, c, i, v);
                else if (v != 0.)
                  tab_float (table, c, i, TAB_RIGHT, v, 8, 0);
@@ -1974,7 +1900,7 @@ display_crosstabulation (void)
            }
 
          if (cmd.miss == CRS_REPORT
-             && is_num_user_missing (rows[r].f, x->v[ROW_VAR]))
+             && is_num_user_missing (rows[r].f, x->vars[ROW_VAR]))
            float_M_suffix (table, n_cols, 0, v);
          else if (v != 0.)
            tab_float (table, n_cols, 0, TAB_RIGHT, v, 8, 0);
@@ -1986,6 +1912,7 @@ display_crosstabulation (void)
   /* Column totals, grand total. */
   {
     int c, j;
+    int last_row = 0;
 
     if (num_cells > 1)
       tab_hline (table, TAL_1, -1, n_cols, 0);
@@ -2022,16 +1949,17 @@ display_crosstabulation (void)
              }
 
            if (cmd.miss == CRS_REPORT && c < n_cols 
-               && is_num_user_missing (cols[c].f, x->v[COL_VAR]))
+               && is_num_user_missing (cols[c].f, x->vars[COL_VAR]))
              float_M_suffix (table, c, j, v);
            else if (v != 0.)
              tab_float (table, c, j, TAB_RIGHT, v, 8, 0);
 
            j++;
          }
+        last_row = j;
       }
 
-    tab_offset (table, -1, tab_row (table) + j);
+    tab_offset (table, -1, tab_row (table) + last_row);
   }
   
   tab_offset (table, 0, -1);
@@ -2188,24 +2116,24 @@ display_risk (void)
       switch (i)
        {
        case 0:
-         if (x->v[COL_VAR]->type == NUMERIC)
+         if (x->vars[COL_VAR]->type == NUMERIC)
            sprintf (buf, _("Odds Ratio for %s (%g / %g)"),
-                    x->v[COL_VAR]->name, c[0].f, c[1].f);
+                    x->vars[COL_VAR]->name, c[0].f, c[1].f);
          else
            sprintf (buf, _("Odds Ratio for %s (%.*s / %.*s)"),
-                    x->v[COL_VAR]->name,
-                    x->v[COL_VAR]->width, c[0].s,
-                    x->v[COL_VAR]->width, c[1].s);
+                    x->vars[COL_VAR]->name,
+                    x->vars[COL_VAR]->width, c[0].s,
+                    x->vars[COL_VAR]->width, c[1].s);
          break;
        case 1:
        case 2:
-         if (x->v[ROW_VAR]->type == NUMERIC)
+         if (x->vars[ROW_VAR]->type == NUMERIC)
            sprintf (buf, _("For cohort %s = %g"),
-                    x->v[ROW_VAR]->name, rows[i - 1].f);
+                    x->vars[ROW_VAR]->name, rows[i - 1].f);
          else
            sprintf (buf, _("For cohort %s = %.*s"),
-                    x->v[ROW_VAR]->name,
-                    x->v[ROW_VAR]->width, rows[i - 1].s);
+                    x->vars[ROW_VAR]->name,
+                    x->vars[ROW_VAR]->width, rows[i - 1].s);
          break;
        }
                   
@@ -2320,9 +2248,9 @@ display_directional (void)
                  if (k == 0)
                    string = NULL;
                  else if (k == 1)
-                   string = x->v[0]->name;
+                   string = x->vars[0]->name;
                  else
-                   string = x->v[1]->name;
+                   string = x->vars[1]->name;
                  
                  tab_text (direct, j, 0, TAB_LEFT | TAT_PRINTF,
                            gettext (stats_names[j][k]), string);
@@ -2346,7 +2274,7 @@ display_directional (void)
 
 /* Returns the value of the gamma (factorial) function for an integer
    argument X. */
-double
+static double
 gamma_int (double x)
 {
   double r = 1;
@@ -2434,8 +2362,7 @@ calc_chisq (double chisq[N_CHISQ], int df[N_CHISQ],
        const double freq = mat[n_cols * r + c];
        const double residual = freq - expected;
     
-       if (expected)
-         chisq[0] += residual * residual / expected;
+        chisq[0] += residual * residual / expected;
        if (freq)
          chisq[1] += freq * log (expected / freq);
       }
@@ -2493,7 +2420,7 @@ calc_chisq (double chisq[N_CHISQ], int df[N_CHISQ],
     }
 
   /* Calculate Mantel-Haenszel. */
-  if (x->v[ROW_VAR]->type == NUMERIC && x->v[COL_VAR]->type == NUMERIC)
+  if (x->vars[ROW_VAR]->type == NUMERIC && x->vars[COL_VAR]->type == NUMERIC)
     {
       double r, ase_0, ase_1;
       calc_r ((double *) rows, (double *) cols, &r, &ase_0, &ase_1);
@@ -2608,8 +2535,7 @@ calc_symmetric (double v[N_SYMMETRIC], double ase[N_SYMMETRIC],
              const double freq = mat[n_cols * r + c];
              const double residual = freq - expected;
     
-             if (expected)
-               Xp += residual * residual / expected;
+              Xp += residual * residual / expected;
            }
       }
 
@@ -2903,7 +2829,7 @@ calc_symmetric (double v[N_SYMMETRIC], double ase[N_SYMMETRIC],
                               + (sqr (W - sum_fii)
                                  * (W * sum_fijri_ci2 - 4.
                                     * sum_rici * sum_rici)
-                                 / hypercube (W * W - sum_rici))));
+                                 / pow4 (W * W - sum_rici))));
 #else
       t[8] = v[8] / ase[8];
 #endif
@@ -3290,6 +3216,34 @@ calc_directional (double v[N_DIRECTIONAL], double ase[N_DIRECTIONAL],
   return 1;
 }
 
+/* A wrapper around data_out() that limits string output to short
+   string width and null terminates the result. */
+static void
+format_short (char *s, const struct fmt_spec *fp, const union value *v)
+{
+  struct fmt_spec fmt_subst;
+
+  /* Limit to short string width. */
+  if (formats[fp->type].cat & FCAT_STRING) 
+    {
+      fmt_subst = *fp;
+
+      assert (fmt_subst.type == FMT_A || fmt_subst.type == FMT_AHEX);
+      if (fmt_subst.type == FMT_A)
+        fmt_subst.w = min (8, fmt_subst.w);
+      else
+        fmt_subst.w = min (16, fmt_subst.w);
+
+      fp = &fmt_subst;
+    }
+
+  /* Format. */
+  data_out (s, fp, v);
+  
+  /* Null terminate. */
+  s[fp->w] = '\0';
+}
+
 /* 
    Local Variables:
    mode: c