? conversions.patch
Index: Smake
===================================================================
RCS file: /cvsroot/pspp/pspp/Smake,v
retrieving revision 1.37
diff -u -p -r1.37 Smake
--- Smake	18 Jul 2006 04:57:01 -0000	1.37
+++ Smake	27 Oct 2006 03:23:20 -0000
@@ -8,6 +8,7 @@ GNULIB_MODULES = \
 	alloca \
 	alloca-opt \
 	assert \
+	byteswap \
 	c-ctype \
 	c-strtod \
 	exit \
Index: src/language/ChangeLog
===================================================================
RCS file: /cvsroot/pspp/pspp/src/language/ChangeLog,v
retrieving revision 1.11
diff -u -p -r1.11 ChangeLog
--- src/language/ChangeLog	20 Oct 2006 03:00:25 -0000	1.11
+++ src/language/ChangeLog	27 Oct 2006 03:23:20 -0000
@@ -1,3 +1,7 @@
+Thu Oct 26 20:19:00 2006  Ben Pfaff  <blp@gnu.org>
+
+	* command.def: Add DEBUG FLOAT FORMAT.
+
 Fri Oct 20 10:59:06 WST 2006 John Darrington <john@darrington.wattle.id.au>
 
 	* command.def: Added additional unimpl commands.
Index: src/language/command.def
===================================================================
RCS file: /cvsroot/pspp/pspp/src/language/command.def,v
retrieving revision 1.8
diff -u -p -r1.8 command.def
--- src/language/command.def	20 Oct 2006 03:00:25 -0000	1.8
+++ src/language/command.def	27 Oct 2006 03:23:21 -0000
@@ -131,6 +131,7 @@ DEF_CMD (S_ANY, F_TESTING, "DEBUG CASEFI
 DEF_CMD (S_ANY, F_TESTING, "DEBUG EVALUATE", cmd_debug_evaluate)
 DEF_CMD (S_ANY, F_TESTING, "DEBUG MOMENTS", cmd_debug_moments)
 DEF_CMD (S_ANY, F_TESTING, "DEBUG POOL", cmd_debug_pool)
+DEF_CMD (S_ANY, F_TESTING, "DEBUG FLOAT FORMAT", cmd_debug_float_format)
 DEF_CMD (S_ANY, F_TESTING, "DEBUG XFORM FAIL", cmd_debug_xform_fail)
 
 /* Unimplemented commands. */
Index: src/language/lexer/ChangeLog
===================================================================
RCS file: /cvsroot/pspp/pspp/src/language/lexer/ChangeLog,v
retrieving revision 1.13
diff -u -p -r1.13 ChangeLog
--- src/language/lexer/ChangeLog	18 Jul 2006 04:57:01 -0000	1.13
+++ src/language/lexer/ChangeLog	27 Oct 2006 03:23:21 -0000
@@ -1,3 +1,9 @@
+Thu Oct 26 20:18:03 2006  Ben Pfaff  <blp@gnu.org>
+
+	* lexer.c (parse_string): Make lexing of binary, hex, and octal
+	strings work (fixes bug #17948).  Allow null bytes in strings, now
+	that there's a use for them (see tests/formats/float-format.sh).
+
 Sun Jul 16 21:03:34 2006  Ben Pfaff  <blp@gnu.org>
 
 	* format-parser.h: New file.  Moved prototypes of format-parser.c
Index: src/language/lexer/lexer.c
===================================================================
RCS file: /cvsroot/pspp/pspp/src/language/lexer/lexer.c,v
retrieving revision 1.9
diff -u -p -r1.9 lexer.c
--- src/language/lexer/lexer.c	20 Oct 2006 11:32:57 -0000	1.9
+++ src/language/lexer/lexer.c	27 Oct 2006 03:23:21 -0000
@@ -1067,6 +1067,9 @@ convert_numeric_string_to_char_string (e
 static int 
 parse_string (enum string_type type)
 {
+  if (type != CHARACTER_STRING)
+    prog++;
+
   /* Accumulate the entire string, joining sections indicated by +
      signs. */
   for (;;)
@@ -1160,24 +1163,6 @@ finish:
       ds_truncate (&tokstr, 255);
     }
       
-  {
-    /* FIXME. */
-    size_t i;
-    int warned = 0;
-
-    for (i = 0; i < ds_length (&tokstr); i++)
-      if (ds_cstr (&tokstr)[i] == 0)
-	{
-	  if (!warned)
-	    {
-	      msg (SE, _("Sorry, literal strings may not contain null "
-			 "characters.  Replacing with spaces."));
-	      warned = 1;
-	    }
-	  ds_cstr (&tokstr)[i] = ' ';
-	}
-  }
-
   return T_STRING;
 }
 	
Index: src/language/tests/ChangeLog
===================================================================
RCS file: /cvsroot/pspp/pspp/src/language/tests/ChangeLog,v
retrieving revision 1.3
diff -u -p -r1.3 ChangeLog
--- src/language/tests/ChangeLog	14 Oct 2006 23:23:17 -0000	1.3
+++ src/language/tests/ChangeLog	27 Oct 2006 03:23:22 -0000
@@ -1,3 +1,9 @@
+Thu Oct 26 20:19:19 2006  Ben Pfaff  <blp@gnu.org>
+
+	* automake.mk: Add float-format.c.
+
+	* float-format.c: New file.
+
 Sat Oct 14 16:21:45 2006  Ben Pfaff  <blp@gnu.org>
 
 	* casefile-test.c: (test_casereader_clone) Free cases that we
Index: src/language/tests/automake.mk
===================================================================
RCS file: /cvsroot/pspp/pspp/src/language/tests/automake.mk,v
retrieving revision 1.2
diff -u -p -r1.2 automake.mk
--- src/language/tests/automake.mk	15 Mar 2006 03:29:11 -0000	1.2
+++ src/language/tests/automake.mk	27 Oct 2006 03:23:22 -0000
@@ -5,4 +5,5 @@ noinst_LIBRARIES += src/language/tests/l
 src_language_tests_libtests_a_SOURCES = \
 	src/language/tests/casefile-test.c \
 	src/language/tests/moments-test.c \
-	src/language/tests/pool-test.c 
+	src/language/tests/pool-test.c \
+	src/language/tests/float-format.c
Index: src/language/tests/float-format.c
===================================================================
RCS file: src/language/tests/float-format.c
diff -N src/language/tests/float-format.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/language/tests/float-format.c	27 Oct 2006 03:23:22 -0000
@@ -0,0 +1,297 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2006 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+
+#include <libpspp/float-format.h>
+
+#include "gettext.h"
+#include <inttypes.h>
+
+#include <language/command.h>
+#include <language/lexer/lexer.h>
+#include <libpspp/assertion.h>
+#include <libpspp/message.h>
+#include <libpspp/str.h>
+
+#define _(msgid) gettext (msgid)
+
+/* Maximum supported size of a floating-point number, in bytes. */
+#define FP_MAX_SIZE 32
+
+/* A floating-point number tagged with its representation. */
+struct fp
+  {
+    enum float_format format;           /* Format. */
+    uint8_t data[FP_MAX_SIZE];          /* Representation. */
+  };
+
+/* Associates a format name with its identifier. */
+struct assoc 
+  {
+    char name[4];
+    enum float_format format;
+  };
+
+/* List of floating-point formats. */
+static const struct assoc fp_formats[] = 
+  {
+    {"ISL", FLOAT_IEEE_SINGLE_LE},
+    {"ISB", FLOAT_IEEE_SINGLE_BE},
+    {"IDL", FLOAT_IEEE_DOUBLE_LE},
+    {"IDB", FLOAT_IEEE_DOUBLE_BE},
+    {"VF", FLOAT_VAX_F},
+    {"VD", FLOAT_VAX_D},
+    {"VG", FLOAT_VAX_G},
+    {"ZS", FLOAT_Z_SHORT},
+    {"ZL", FLOAT_Z_LONG},
+    {"X", FLOAT_HEX},
+    {"FP", FLOAT_FP},
+  };
+static const size_t format_cnt = sizeof fp_formats / sizeof *fp_formats;
+
+/* Parses a floating-point format name into *FORMAT,
+   and returns success. */
+static bool
+parse_float_format (enum float_format *format) 
+{
+  size_t i;
+
+  for (i = 0; i < format_cnt; i++)
+    if (lex_match_id (fp_formats[i].name)) 
+      {
+        *format = fp_formats[i].format;
+        return true;
+      }
+  lex_error ("expecting floating-point format identifier");
+  return false;
+}
+
+/* Returns the name for the given FORMAT. */
+static const char *
+get_float_format_name (enum float_format format) 
+{
+  size_t i;
+
+  for (i = 0; i < format_cnt; i++)
+    if (fp_formats[i].format == format) 
+      return fp_formats[i].name;
+
+  NOT_REACHED ();
+}
+
+/* Parses a number in the form FORMAT(STRING), where FORMAT is
+   the name of the format and STRING gives the number's
+   representation.  Also supports ordinary floating-point numbers
+   written in decimal notation.  Returns success. */
+static bool
+parse_fp (struct fp *fp) 
+{
+  if (lex_is_number ()) 
+    {
+      double number = lex_number ();
+      fp->format = FLOAT_NATIVE_DOUBLE;
+      memcpy (fp->data, &number, sizeof number);
+      lex_get ();
+    }
+  else if (token == T_ID)
+    {
+      size_t length;
+      
+      if (!parse_float_format (&fp->format)
+          || !lex_force_match ('(')
+          || !lex_force_string ())
+        return false;
+
+      length = ds_length (&tokstr);
+      if (fp->format != FLOAT_HEX) 
+        {
+          if (length != float_get_size (fp->format)) 
+            {
+              msg (SE, "%d-byte string needed but %d-byte string supplied.",
+                   (int) float_get_size (fp->format), (int) length);
+              return false;
+            }
+          assert (length <= sizeof fp->data);
+          memcpy (fp->data, ds_data (&tokstr), length); 
+        }
+      else 
+        {
+          if (length >= sizeof fp->data) 
+            {
+              msg (SE, "Hexadecimal floating constant too long.");
+              return false;
+            }
+          strncpy ((char *) fp->data, ds_cstr (&tokstr), sizeof fp->data);
+        }
+
+      lex_get ();
+      if (!lex_force_match (')'))
+        return false;
+    }
+  else
+    {
+      lex_error (NULL);
+      return false;
+    }
+  return true;
+}
+
+/* Renders SRC, which contains SIZE bytes of a floating-point
+   number in the given FORMAT, as relatively human-readable
+   null-terminated string in DST, which must be large enough to
+   hold the output. */
+static void
+make_printable (enum float_format format, const void *src_,
+                char *dst, size_t size) 
+{
+  if (format != FLOAT_HEX) 
+    {
+      const uint8_t *src = src_;
+      while (size-- > 0) 
+        {
+          sprintf (dst, "%02x", *src++);
+          dst += 2;
+        }
+      *dst = '\0'; 
+    }
+  else 
+    strncpy (dst, src_, size + 1);
+}
+
+/* Checks that RESULT is identical to TO.
+   If so, returns false.
+   If not, issues a helpful error message that includes the given
+   CONVERSION_TYPE and the value that was converted FROM, and
+   returns true. */
+static bool
+mismatch (const struct fp *from, const struct fp *to, char *result,
+          const char *conversion_type) 
+{
+  size_t to_size = float_get_size (to->format);
+  if (!memcmp (to->data, result, to_size))
+    return false;
+  else
+    {
+      size_t from_size = float_get_size (from->format);
+      char original[FP_MAX_SIZE * 2 + 1];
+      char expected[FP_MAX_SIZE * 2 + 1];
+      char actual[FP_MAX_SIZE * 2 + 1];
+      make_printable (from->format, from->data, original, from_size);
+      make_printable (to->format, to->data, expected, to_size);
+      make_printable (to->format, result, actual, to_size);
+      msg (SE,
+           "%s conversion of %s from %s to %s should have produced %s "
+           "but actually produced %s.",
+           conversion_type,
+           original, get_float_format_name (from->format),
+           get_float_format_name (to->format), expected,
+           actual);
+      return true;
+    }
+}
+
+/* Checks that converting FROM into the format of TO yields
+   exactly the data in TO. */
+static bool
+verify_conversion (const struct fp *from, const struct fp *to) 
+{
+  char tmp1[FP_MAX_SIZE], tmp2[FP_MAX_SIZE];
+  
+  /* First try converting directly. */
+  float_convert (from->format, from->data, to->format, tmp1);
+  if (mismatch (from, to, tmp1, "Direct"))
+    return false;
+  
+  /* Then convert via FLOAT_FP to prevent short-circuiting that
+     float_convert() does for some conversions (e.g. little<->big
+     endian for IEEE formats). */
+  float_convert (from->format, from->data, FLOAT_FP, tmp1);
+  float_convert (FLOAT_FP, tmp1, to->format, tmp2);
+  if (mismatch (from, to, tmp2, "Indirect"))
+    return false;
+  
+  return true;
+}
+
+/* Executes the DEBUG FLOAT FORMAT command. */
+int
+cmd_debug_float_format (struct dataset *ds UNUSED) 
+{
+  struct fp fp[16];
+  size_t fp_cnt = 0;
+  bool bijective = false;
+  bool ok;
+
+  for (;;)
+    {
+      if (fp_cnt >= sizeof fp / sizeof *fp) 
+        {
+          msg (SE, "Too many values in single command.");
+          return CMD_FAILURE;
+        }
+      if (!parse_fp (&fp[fp_cnt++]))
+        return CMD_FAILURE;
+
+      if (token == '.' && fp_cnt > 1)
+        break;
+      else if (!lex_force_match ('='))
+        return CMD_FAILURE;
+      
+      if (fp_cnt == 1) 
+        {
+          if (lex_match ('='))
+            bijective = true;
+          else if (lex_match (T_GT))
+            bijective = false;
+          else 
+            {
+              lex_error (NULL);
+              return CMD_FAILURE;
+            }
+        }
+      else 
+        {
+          if ((bijective && !lex_force_match ('='))
+              || (!bijective && !lex_force_match (T_GT)))
+            return CMD_FAILURE;
+        }
+    }
+
+  ok = true;
+  if (bijective) 
+    {
+      size_t i, j;
+
+      for (i = 0; i < fp_cnt; i++)
+        for (j = 0; j < fp_cnt; j++)
+          if (!verify_conversion (&fp[i], &fp[j])) 
+            ok = false;
+    }
+  else 
+    {
+      size_t i;
+
+      for (i = 1; i < fp_cnt; i++)
+        if (!verify_conversion (&fp[i - 1], &fp[i]))
+          ok = false;
+    }
+
+  return ok ? CMD_SUCCESS : CMD_FAILURE;
+}
Index: src/libpspp/ChangeLog
===================================================================
RCS file: /cvsroot/pspp/pspp/src/libpspp/ChangeLog,v
retrieving revision 1.36
diff -u -p -r1.36 ChangeLog
--- src/libpspp/ChangeLog	15 Oct 2006 01:51:02 -0000	1.36
+++ src/libpspp/ChangeLog	27 Oct 2006 03:23:22 -0000
@@ -1,3 +1,19 @@
+Thu Oct 26 20:19:50 2006  Ben Pfaff  <blp@gnu.org>
+
+	* automake.mk: Add the new files.
+
+	* legacy-encoding.c: New file.
+
+	* legacy-encoding.h: New file.
+
+	* float-format.c: New file.
+
+	* float-format.h: New file.
+
+	* integer-format.c: New file.
+
+	* integer-format.h: New file.
+
 Sun Oct 15 09:49:50 WST 2006 John Darrington <john@darrington.wattle.id.au>
 
 	* hash.c hash.h: Added hsh_create_pool, a hash which uses a pool
Index: src/libpspp/automake.mk
===================================================================
RCS file: /cvsroot/pspp/pspp/src/libpspp/automake.mk,v
retrieving revision 1.14
diff -u -p -r1.14 automake.mk
--- src/libpspp/automake.mk	15 Jul 2006 04:38:58 -0000	1.14
+++ src/libpspp/automake.mk	27 Oct 2006 03:23:22 -0000
@@ -10,15 +10,21 @@ src_libpspp_libpspp_a_SOURCES = \
 	src/libpspp/alloc.c \
 	src/libpspp/alloc.h \
 	src/libpspp/bit-vector.h \
+	src/libpspp/legacy-encoding.c \
+	src/libpspp/legacy-encoding.h \
 	src/libpspp/copyleft.c \
 	src/libpspp/copyleft.h \
 	src/libpspp/compiler.h \
+	src/libpspp/float-format.c \
+	src/libpspp/float-format.h \
 	src/libpspp/freaderror.c \
 	src/libpspp/freaderror.h \
 	src/libpspp/hash.c \
 	src/libpspp/hash.h \
 	src/libpspp/i18n.c \
 	src/libpspp/i18n.h \
+	src/libpspp/integer-format.c \
+	src/libpspp/integer-format.h \
 	src/libpspp/ll.c \
 	src/libpspp/ll.h \
 	src/libpspp/llx.c \
Index: src/libpspp/float-format.c
===================================================================
RCS file: src/libpspp/float-format.c
diff -N src/libpspp/float-format.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/libpspp/float-format.c	27 Oct 2006 03:23:23 -0000
@@ -0,0 +1,1078 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2006 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+
+#include <libpspp/float-format.h>
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdlib.h>
+
+#include <libpspp/assertion.h>
+#include <libpspp/integer-format.h>
+#include <libpspp/str.h>
+
+#include "error.h"
+#include <byteswap.h>
+
+/* Neutral intermediate representation for binary floating-point numbers. */
+struct fp 
+  {
+    enum 
+      {
+        FINITE,         /* Finite number (normalized or denormalized). */
+        INFINITE,       /* Positive or negative infinity. */
+        NAN,            /* Not a number. */
+        
+        ZERO,           /* Positive or negative zero. */
+        MISSING,        /* System missing. */
+        LOWEST,         /* LOWEST on e.g. missing values. */
+        HIGHEST,        /* HIGHEST on e.g. missing values. */
+        RESERVED        /* Special Vax representation. */
+      }
+    class;
+    
+    enum
+      { 
+        POSITIVE,
+        NEGATIVE
+      }
+    sign;
+
+    /* class == FINITE: The number has value "fraction *
+       2**exponent", considering bit 63 in fraction to be just
+       right of the decimal point.
+
+       class == NAN: The fraction holds the significand, with its
+       leftmost bit in bit 63, so that signaling and quiet NaN
+       values can be preserved.
+
+       Unused for other classes. */       
+    uint64_t fraction;
+    int exponent;
+  };
+
+static void extract_number (enum float_format, const void *, struct fp *);
+static void assemble_number (enum float_format, struct fp *, void *);
+
+static inline uint16_t get_uint16 (const void *);
+static inline uint32_t get_uint32 (const void *);
+static inline uint64_t get_uint64 (const void *);
+
+static inline void put_uint16 (uint16_t, void *);
+static inline void put_uint32 (uint32_t, void *);
+static inline void put_uint64 (uint64_t, void *);
+
+/* Converts SRC from format FROM to format TO, storing the
+   converted value into DST.
+   SRC and DST are permitted to arbitrarily overlap. */
+void
+float_convert (enum float_format from, const void *src, 
+               enum float_format to, void *dst) 
+{
+  if (from != to) 
+    {
+      if ((from == FLOAT_IEEE_SINGLE_LE || from == FLOAT_IEEE_SINGLE_BE)
+          && (to == FLOAT_IEEE_SINGLE_LE || to == FLOAT_IEEE_SINGLE_BE))
+        put_uint32 (bswap_32 (get_uint32 (src)), dst);
+      else if ((from == FLOAT_IEEE_DOUBLE_LE || from == FLOAT_IEEE_DOUBLE_BE)
+               && (to == FLOAT_IEEE_DOUBLE_LE || to == FLOAT_IEEE_DOUBLE_BE))
+        put_uint64 (bswap_64 (get_uint64 (src)), dst);
+      else 
+        {
+          struct fp fp;
+          extract_number (from, src, &fp);
+          assemble_number (to, &fp, dst);
+        } 
+    }
+  else 
+    {
+      if (src != dst)
+        memmove (dst, src, float_get_size (from));
+    }
+}
+
+/* Returns the number of bytes in a number in the given
+   FORMAT. */
+size_t
+float_get_size (enum float_format format) 
+{
+  switch (format) 
+    {
+    case FLOAT_IEEE_SINGLE_LE:
+    case FLOAT_IEEE_SINGLE_BE:
+    case FLOAT_VAX_F:
+    case FLOAT_Z_SHORT:
+      return 4;
+      
+    case FLOAT_IEEE_DOUBLE_LE:
+    case FLOAT_IEEE_DOUBLE_BE:
+    case FLOAT_VAX_D:
+    case FLOAT_VAX_G:
+    case FLOAT_Z_LONG:
+      return 8;
+      
+    case FLOAT_FP:
+      return sizeof (struct fp);
+      
+    case FLOAT_HEX:
+      return 32;
+    }
+
+  NOT_REACHED ();
+}
+
+/* Attempts to identify the floating point format(s) in which the
+   LENGTH bytes in NUMBER represent the given EXPECTED_VALUE.
+   Returns the number of matches, which may be zero, one, or
+   greater than one.  If a positive value is returned, then the
+   most likely candidate format (based on how common the formats
+   are in practice) is stored in *BEST_GUESS. */
+int
+float_identify (double expected_value, const void *number, size_t length,
+                enum float_format *best_guess) 
+{
+  /* Candidates for identification in order of decreasing
+     preference. */
+  enum float_format candidates[] = 
+    {
+      FLOAT_IEEE_SINGLE_LE,
+      FLOAT_IEEE_SINGLE_BE,
+      FLOAT_IEEE_DOUBLE_LE,
+      FLOAT_IEEE_DOUBLE_BE,
+      FLOAT_VAX_F,
+      FLOAT_VAX_D,
+      FLOAT_VAX_G,
+      FLOAT_Z_SHORT,
+      FLOAT_Z_LONG,
+    };
+  const size_t candidate_cnt = sizeof candidates / sizeof *candidates;
+
+  enum float_format *p;
+  int match_cnt;
+
+  match_cnt = 0;
+  for (p = candidates; p < candidates + candidate_cnt; p++)
+    if (float_get_size (*p) == length)
+      {
+        char tmp[8];
+        assert (sizeof tmp >= float_get_size (*p));
+        float_convert (FLOAT_NATIVE_DOUBLE, &expected_value, *p, tmp);
+        if (!memcmp (tmp, number, length) && match_cnt++ == 0)
+          *best_guess = *p;
+      }
+  return match_cnt;
+}
+
+/* Returns CNT bits in X starting from the given bit OFS. */
+static inline uint64_t
+get_bits (uint64_t x, int ofs, int cnt) 
+{
+  assert (ofs >= 0 && ofs < 64);
+  assert (cnt > 0 && cnt < 64);
+  assert (ofs + cnt <= 64);
+  return (x >> ofs) & ((UINT64_C(1) << cnt) - 1);
+}
+
+/* Returns the 16-bit unsigned integer at P,
+   which need not be aligned. */
+static inline uint16_t
+get_uint16 (const void *p) 
+{
+  uint16_t x;
+  memcpy (&x, p, sizeof x);
+  return x;
+}
+
+/* Returns the 32-bit unsigned integer at P,
+   which need not be aligned. */
+static inline uint32_t
+get_uint32 (const void *p) 
+{
+  uint32_t x;
+  memcpy (&x, p, sizeof x);
+  return x;
+}
+
+/* Returns the 64-bit unsigned integer at P,
+   which need not be aligned. */
+static inline uint64_t
+get_uint64 (const void *p) 
+{
+  uint64_t x;
+  memcpy (&x, p, sizeof x);
+  return x;
+}
+
+/* Stores 16-bit unsigned integer X at P,
+   which need not be aligned. */
+static inline void
+put_uint16 (uint16_t x, void *p) 
+{
+  memcpy (p, &x, sizeof x);
+}
+
+/* Stores 32-bit unsigned integer X at P,
+   which need not be aligned. */
+static inline void
+put_uint32 (uint32_t x, void *p) 
+{
+  memcpy (p, &x, sizeof x);
+}
+
+/* Stores 64-bit unsigned integer X at P,
+   which need not be aligned. */
+static inline void
+put_uint64 (uint64_t x, void *p) 
+{
+  memcpy (p, &x, sizeof x);
+}
+
+/* Returns NATIVE converted to a form that, when stored in
+   memory, will be in little-endian byte order. */
+static inline uint16_t
+native_to_le16 (uint16_t native) 
+{
+  return INTEGER_NATIVE == INTEGER_LSB_FIRST ? native : bswap_16 (native);
+}
+
+/* Returns NATIVE converted to a form that, when stored in
+   memory, will be in big-endian byte order. */
+static inline uint16_t
+native_to_be16 (uint16_t native) 
+{
+  return INTEGER_NATIVE == INTEGER_MSB_FIRST ? native : bswap_16 (native);
+}
+
+/* Returns NATIVE converted to a form that, when stored in
+   memory, will be in VAX-endian byte order. */
+static inline uint16_t
+native_to_vax16 (uint16_t native) 
+{
+  return native_to_le16 (native);
+}
+
+/* Returns NATIVE converted to a form that, when stored in
+   memory, will be in little-endian byte order. */
+static inline uint32_t
+native_to_le32 (uint32_t native) 
+{
+  return INTEGER_NATIVE == INTEGER_LSB_FIRST ? native : bswap_32 (native);
+}
+
+/* Returns NATIVE converted to a form that, when stored in
+   memory, will be in big-endian byte order. */
+static inline uint32_t
+native_to_be32 (uint32_t native) 
+{
+  return INTEGER_NATIVE == INTEGER_MSB_FIRST ? native : bswap_32 (native);
+}
+
+/* Returns NATIVE converted to a form that, when stored in
+   memory, will be in VAX-endian byte order. */
+static inline uint32_t
+native_to_vax32 (uint32_t native) 
+{
+  return native_to_be32 (((native & 0xff00ff00) >> 8) |
+                         ((native & 0x00ff00ff) << 8));
+}
+
+/* Returns NATIVE converted to a form that, when stored in
+   memory, will be in little-endian byte order. */
+static inline uint64_t
+native_to_le64 (uint64_t native) 
+{
+  return INTEGER_NATIVE == INTEGER_LSB_FIRST ? native : bswap_64 (native);
+}
+
+/* Returns NATIVE converted to a form that, when stored in
+   memory, will be in big-endian byte order. */
+static inline uint64_t
+native_to_be64 (uint64_t native) 
+{
+  return INTEGER_NATIVE == INTEGER_MSB_FIRST ? native : bswap_64 (native);
+}
+
+/* Returns NATIVE converted to a form that, when stored in
+   memory, will be in VAX-endian byte order. */
+static inline uint64_t
+native_to_vax64 (uint64_t native) 
+{
+  return native_to_be64 (((native & UINT64_C(0xff00ff0000000000)) >> 40) |
+                         ((native & UINT64_C(0x00ff00ff00000000)) >> 24) |
+                         ((native & UINT64_C(0x00000000ff00ff00)) << 24) |
+                         ((native & UINT64_C(0x0000000000ff00ff)) << 40));
+}
+
+/* Given LE, obtained from memory in little-endian format,
+   returns its value. */
+static inline uint16_t
+le_to_native16 (uint16_t le) 
+{
+  return INTEGER_NATIVE == INTEGER_LSB_FIRST ? le : bswap_16 (le);
+}
+
+/* Given BE, obtained from memory in big-endian format, returns
+   its value. */
+static inline uint16_t
+be_to_native16 (uint16_t be) 
+{
+  return INTEGER_NATIVE == INTEGER_MSB_FIRST ? be : bswap_16 (be);
+}
+
+/* Given VAX, obtained from memory in VAX-endian format, returns
+   its value. */
+static inline uint16_t
+vax_to_native16 (uint16_t vax) 
+{
+  return le_to_native16 (vax);
+}
+
+/* Given LE, obtained from memory in little-endian format,
+   returns its value. */
+static inline uint32_t
+le_to_native32 (uint32_t le) 
+{
+  return INTEGER_NATIVE == INTEGER_LSB_FIRST ? le : bswap_32 (le);
+}
+
+/* Given BE, obtained from memory in big-endian format, returns
+   its value. */
+static inline uint32_t
+be_to_native32 (uint32_t be) 
+{
+  return INTEGER_NATIVE == INTEGER_MSB_FIRST ? be : bswap_32 (be);
+}
+
+/* Given VAX, obtained from memory in VAX-endian format, returns
+   its value. */
+static inline uint32_t
+vax_to_native32 (uint32_t vax) 
+{
+  uint32_t be = be_to_native32 (vax);
+  return ((be & 0xff00ff00) >> 8) | ((be & 0x00ff00ff) << 8);
+}
+
+/* Given LE, obtained from memory in little-endian format,
+   returns its value. */
+static inline uint64_t
+le_to_native64 (uint64_t le) 
+{
+  return INTEGER_NATIVE == INTEGER_LSB_FIRST ? le : bswap_64 (le);
+}
+
+/* Given BE, obtained from memory in big-endian format, returns
+   its value. */
+static inline uint64_t
+be_to_native64 (uint64_t be) 
+{
+  return INTEGER_NATIVE == INTEGER_MSB_FIRST ? be : bswap_64 (be);
+}
+
+/* Given VAX, obtained from memory in VAX-endian format, returns
+   its value. */
+static inline uint64_t
+vax_to_native64 (uint64_t vax) 
+{
+  uint64_t be = be_to_native64 (vax);
+  return (((be & UINT64_C(0xff00ff0000000000)) >> 40) |
+          ((be & UINT64_C(0x00ff00ff00000000)) >> 24) |
+          ((be & UINT64_C(0x00000000ff00ff00)) << 24) |
+          ((be & UINT64_C(0x0000000000ff00ff)) << 40));
+}
+
+static void extract_ieee (uint64_t, int exp_bits, int frac_bits, struct fp *);
+static void extract_vax (uint64_t, int exp_bits, int frac_bits, struct fp *);
+static void extract_z (uint64_t, int exp_bits, int frac_bits, struct fp *);
+static void extract_hex (const char *, struct fp *);
+
+/* Converts the number at BITS from format TYPE into neutral
+   format at FP. */
+static void
+extract_number (enum float_format type, const void *bits, struct fp *fp) 
+{
+  switch (type) 
+    {
+    case FLOAT_IEEE_SINGLE_LE:
+      extract_ieee (le_to_native32 (get_uint32 (bits)), 8, 23, fp);
+      break;
+    case FLOAT_IEEE_SINGLE_BE:
+      extract_ieee (be_to_native32 (get_uint32 (bits)), 8, 23, fp);
+      break;
+    case FLOAT_IEEE_DOUBLE_LE:
+      extract_ieee (le_to_native64 (get_uint64 (bits)), 11, 52, fp);
+      break;
+    case FLOAT_IEEE_DOUBLE_BE:
+      extract_ieee (be_to_native64 (get_uint64 (bits)), 11, 52, fp);
+      break;
+
+    case FLOAT_VAX_F:
+      extract_vax (vax_to_native32 (get_uint32 (bits)), 8, 23, fp);
+      break;
+    case FLOAT_VAX_D:
+      extract_vax (vax_to_native64 (get_uint64 (bits)), 8, 55, fp);
+      break;
+    case FLOAT_VAX_G:
+      extract_vax (vax_to_native64 (get_uint64 (bits)), 11, 52, fp);
+      break;
+
+    case FLOAT_Z_SHORT:
+      extract_z (be_to_native32 (get_uint32 (bits)), 7, 24, fp);
+      break;
+    case FLOAT_Z_LONG:
+      extract_z (be_to_native64 (get_uint64 (bits)), 7, 56, fp);
+      break;
+
+    case FLOAT_FP:
+      memcpy (fp, bits, sizeof *fp);
+      break;
+    case FLOAT_HEX:
+      extract_hex (bits, fp);
+      break;
+    }
+
+  assert (!(fp->class == FINITE && fp->fraction == 0));
+}
+
+/* Converts the IEEE format number in BITS, which has EXP_BITS of
+   exponent and FRAC_BITS of fraction, into neutral format at
+   FP. */
+static void
+extract_ieee (uint64_t bits, int exp_bits, int frac_bits, struct fp *fp) 
+{
+  const int bias = (1 << (exp_bits - 1)) - 1;
+  const uint64_t max_raw_frac = (UINT64_C(1) << frac_bits) - 1;
+  const int max_raw_exp = (1 << exp_bits) - 1;
+
+  const uint64_t raw_frac = get_bits (bits, 0, frac_bits);
+  const int raw_exp = get_bits (bits, frac_bits, exp_bits);
+  const bool raw_sign = get_bits (bits, frac_bits + exp_bits, 1);
+
+  if (raw_sign && raw_exp == max_raw_exp - 1 && raw_frac == max_raw_frac - 1)
+    fp->class = LOWEST;
+  else if (raw_exp == max_raw_exp - 1 && raw_frac == max_raw_frac)
+    fp->class = raw_sign ? MISSING : HIGHEST;
+  else if (raw_exp == max_raw_exp) 
+    {
+      if (raw_frac == 0)
+        fp->class = INFINITE;
+      else 
+        {
+          fp->class = NAN;
+          fp->fraction = raw_frac << (64 - frac_bits);
+        }
+    }
+  else if (raw_exp == 0) 
+    {
+      if (raw_frac != 0) 
+        {
+          fp->class = FINITE;
+          fp->exponent = 1 - bias;
+          fp->fraction = raw_frac << (64 - frac_bits);
+        }
+      else 
+        fp->class = ZERO;
+    }
+  else 
+    {
+      fp->class = FINITE;
+      fp->exponent = raw_exp - bias + 1;
+      fp->fraction = (raw_frac << (64 - frac_bits - 1)) | (UINT64_C(1) << 63);
+    }
+
+  fp->sign = raw_sign ? NEGATIVE : POSITIVE;
+}
+
+/* Converts the VAX format number in BITS, which has EXP_BITS of
+   exponent and FRAC_BITS of fraction, into neutral format at
+   FP. */
+static void
+extract_vax (uint64_t bits, int exp_bits, int frac_bits, struct fp *fp) 
+{
+  const int bias = 1 << (exp_bits - 1);
+  const uint64_t max_raw_frac = (UINT64_C(1) << frac_bits) - 1;
+  const int max_raw_exp = (1 << exp_bits) - 1;
+
+  const uint64_t raw_frac = get_bits (bits, 0, frac_bits);
+  const int raw_exp = get_bits (bits, frac_bits, exp_bits);
+  const bool raw_sign = get_bits (bits, frac_bits + exp_bits, 1);
+
+  if (raw_sign && raw_exp == max_raw_exp && raw_frac == max_raw_frac - 1)
+    fp->class = LOWEST;
+  else if (raw_exp == max_raw_exp && raw_frac == max_raw_frac)
+    fp->class = raw_sign ? MISSING : HIGHEST;
+  else if (raw_exp == 0) 
+    fp->class = raw_sign == 0 ? ZERO : RESERVED;
+  else
+    {
+      fp->class = FINITE;
+      fp->fraction = (raw_frac << (64 - frac_bits - 1)) | (UINT64_C(1) << 63);
+      fp->exponent = raw_exp - bias;
+    }
+
+  fp->sign = raw_sign ? NEGATIVE : POSITIVE;
+}
+
+/* Converts the Z architecture format number in BITS, which has
+   EXP_BITS of exponent and FRAC_BITS of fraction, into neutral
+   format at FP. */
+static void
+extract_z (uint64_t bits, int exp_bits, int frac_bits, struct fp *fp) 
+{
+  const int bias = 1 << (exp_bits - 1);
+  const uint64_t max_raw_frac = (UINT64_C(1) << frac_bits) - 1;
+  const int max_raw_exp = (1 << exp_bits) - 1;
+
+  uint64_t raw_frac = get_bits (bits, 0, frac_bits);
+  int raw_exp = get_bits (bits, frac_bits, exp_bits);
+  int raw_sign = get_bits (bits, frac_bits + exp_bits, 1);
+
+  fp->sign = raw_sign ? NEGATIVE : POSITIVE;
+  if (raw_exp == max_raw_exp && raw_frac == max_raw_frac)
+    fp->class = raw_sign ? MISSING : HIGHEST;
+  else if (raw_sign && raw_exp == max_raw_exp && raw_frac == max_raw_frac - 1)
+    fp->class = LOWEST;
+  else if (raw_frac != 0) 
+    {
+      fp->class = FINITE;
+      fp->fraction = raw_frac << (64 - frac_bits);
+      fp->exponent = (raw_exp - bias) * 4; 
+    }
+  else 
+    fp->class = ZERO;
+}
+
+/* Returns the integer value of hex digit C. */
+static int
+hexit_value (int c)
+{
+  const char s[] = "0123456789abcdef";
+  const char *cp = strchr (s, tolower ((unsigned char) c));
+
+  assert (cp != NULL);
+  return cp - s;
+}
+
+/* Parses a hexadecimal floating point number string at S (useful
+   for testing) into neutral format at FP. */
+static void
+extract_hex (const char *s, struct fp *fp) 
+{
+  if (*s == '-') 
+    {
+      fp->sign = NEGATIVE;
+      s++;
+    }
+  else
+    fp->sign = POSITIVE;
+  
+  if (!strcmp (s, "Infinity")) 
+    fp->class = INFINITE;
+  else if (!strcmp (s, "Missing")) 
+    fp->class = MISSING;
+  else if (!strcmp (s, "Lowest")) 
+    fp->class = LOWEST;
+  else if (!strcmp (s, "Highest")) 
+    fp->class = HIGHEST;
+  else if (!strcmp (s, "Reserved")) 
+    fp->class = RESERVED;
+  else 
+    {
+      int offset;
+      
+      if (!memcmp (s, "NaN:", 4)) 
+        {
+          fp->class = NAN;
+          s += 4;
+        }
+      else 
+        fp->class = FINITE;
+
+      if (*s == '.')
+        s++;
+
+      fp->exponent = 0;
+      fp->fraction = 0;
+      offset = 60;
+      for (; isxdigit ((unsigned char) *s); s++)
+        if (offset >= 0) 
+          {
+            uint64_t digit = hexit_value (*s);
+            fp->fraction += digit << offset;
+            offset -= 4; 
+          }
+
+      if (fp->class == FINITE) 
+        {
+          if (fp->fraction == 0) 
+            fp->class = ZERO;
+          else if (*s == 'p') 
+            {
+              char *tail;
+              fp->exponent += strtol (s + 1, &tail, 10);
+              s = tail;
+            } 
+        }
+    }
+}
+
+static uint64_t assemble_ieee (struct fp *, int exp_bits, int frac_bits);
+static uint64_t assemble_vax (struct fp *, int exp_bits, int frac_bits);
+static uint64_t assemble_z (struct fp *, int exp_bits, int frac_bits);
+static void assemble_hex (struct fp *, void *);
+
+/* Converts the neutral format floating point number in FP into
+   format TYPE at NUMBER.  May modify FP as part of the
+   process. */
+static void
+assemble_number (enum float_format type, struct fp *fp, void *number) 
+{
+  switch (type) 
+    {
+    case FLOAT_IEEE_SINGLE_LE:
+      put_uint32 (native_to_le32 (assemble_ieee (fp, 8, 23)), number);
+      break;
+    case FLOAT_IEEE_SINGLE_BE:
+      put_uint32 (native_to_be32 (assemble_ieee (fp, 8, 23)), number);
+      break;
+    case FLOAT_IEEE_DOUBLE_LE:
+      put_uint64 (native_to_le64 (assemble_ieee (fp, 11, 52)), number);
+      break;
+    case FLOAT_IEEE_DOUBLE_BE:
+      put_uint64 (native_to_be64 (assemble_ieee (fp, 11, 52)), number);
+      break;
+
+    case FLOAT_VAX_F:
+      put_uint32 (native_to_vax32 (assemble_vax (fp, 8, 23)), number);
+      break;
+    case FLOAT_VAX_D:
+      put_uint64 (native_to_vax64 (assemble_vax (fp, 8, 55)), number);
+      break;
+    case FLOAT_VAX_G:
+      put_uint64 (native_to_vax64 (assemble_vax (fp, 11, 52)), number);
+      break;
+
+    case FLOAT_Z_SHORT:
+      put_uint64 (native_to_be32 (assemble_z (fp, 7, 24)), number);
+      break;
+    case FLOAT_Z_LONG:
+      put_uint64 (native_to_be64 (assemble_z (fp, 7, 56)), number);
+      break;
+
+    case FLOAT_FP:
+      memcpy (number, fp, sizeof *fp);
+      break;
+    case FLOAT_HEX:
+      assemble_hex (fp, number);
+      break;
+    }
+}
+
+/* Rounds off FP's fraction to FRAC_BITS bits of precision.
+   Halfway values are rounded to even. */
+static void
+normalize_and_round_fp (struct fp *fp, int frac_bits) 
+{
+  assert (fp->class == FINITE);
+  assert (fp->fraction != 0);
+
+  /* Make sure that the leading fraction bit is 1. */
+  while (!(fp->fraction & (UINT64_C(1) << 63))) 
+    {
+      fp->fraction <<= 1;
+      fp->exponent--;
+    }
+
+  if (frac_bits < 64) 
+    {
+      uint64_t last_frac_bit = UINT64_C(1) << (64 - frac_bits);
+      uint64_t decision_bit = last_frac_bit >> 1;
+      if (fp->fraction & decision_bit
+          && (fp->fraction & (decision_bit - 1)
+              || fp->fraction & last_frac_bit))
+        {
+          fp->fraction += last_frac_bit;
+          if ((fp->fraction >> 63) == 0) 
+            {
+              fp->fraction = UINT64_C(1) << 63;
+              fp->exponent++;
+            }
+        }
+
+      /* Mask off all but FRAC_BITS high-order bits.
+         If we rounded up, then these bits no longer have
+         meaningful values. */
+      fp->fraction &= ~(last_frac_bit - 1);
+    }
+}
+
+/* Converts the neutral format floating point number in FP into
+   IEEE format with EXP_BITS exponent bits and FRAC_BITS fraction
+   bits, and returns the value. */
+static uint64_t
+assemble_ieee (struct fp *fp, int exp_bits, int frac_bits) 
+{
+  const uint64_t max_raw_frac = (UINT64_C(1) << frac_bits) - 1;
+
+  const int bias = (1 << (exp_bits - 1)) - 1;
+  const int max_raw_exp = (1 << exp_bits) - 1;
+  const int min_norm_exp = 1 - bias;
+  const int min_denorm_exp = min_norm_exp - frac_bits;
+  const int max_norm_exp = max_raw_exp - 1 - bias;
+
+  uint64_t raw_frac;
+  int raw_exp;
+  bool raw_sign;
+
+  raw_sign = fp->sign != POSITIVE;
+
+  switch (fp->class) 
+    {
+    case FINITE: 
+      normalize_and_round_fp (fp, frac_bits + 1);
+      if (fp->exponent - 1 > max_norm_exp)
+        {
+          /* Overflow to infinity. */
+          raw_exp = max_raw_exp;
+          raw_frac = 0;
+        }
+      else if (fp->exponent - 1 >= min_norm_exp) 
+        {
+          /* Normal. */
+          raw_frac = (fp->fraction << 1) >> (64 - frac_bits);
+          raw_exp = (fp->exponent - 1) + bias;
+        }
+      else if (fp->exponent - 1 >= min_denorm_exp)
+        {
+          /* Denormal. */
+          const int denorm_shift = min_norm_exp - fp->exponent;
+          raw_frac = (fp->fraction >> (64 - frac_bits)) >> denorm_shift;
+          raw_exp = 0;
+        }
+      else 
+        {
+          /* Underflow to zero. */
+          raw_frac = 0;
+          raw_exp = 0;
+        }
+      break;
+
+    case INFINITE:
+      raw_frac = 0;
+      raw_exp = max_raw_exp;
+      break;
+
+    case NAN:
+      raw_frac = fp->fraction >> (64 - frac_bits);
+      if (raw_frac == 0)
+        raw_frac = 1;
+      raw_exp = max_raw_exp;
+      break;
+
+    case ZERO:
+      raw_frac = 0;
+      raw_exp = 0;
+      break;
+
+    case MISSING:
+      raw_sign = 1;
+      raw_exp = max_raw_exp - 1;
+      raw_frac = max_raw_frac;
+      break;
+
+    case LOWEST:
+      raw_sign = 1;
+      raw_exp = max_raw_exp - 1;
+      raw_frac = max_raw_frac - 1;
+      break;
+
+    case HIGHEST:
+      raw_sign = 0;
+      raw_exp = max_raw_exp - 1;
+      raw_frac = max_raw_frac;
+      break;
+
+    case RESERVED:
+      /* Convert to what processors commonly treat as signaling NAN. */
+      raw_frac = (UINT64_C(1) << frac_bits) - 1;
+      raw_exp = max_raw_exp;
+      break;
+
+    default:
+      NOT_REACHED ();
+    }
+
+  return (((uint64_t) raw_sign << (frac_bits + exp_bits))
+          | ((uint64_t) raw_exp << frac_bits)
+          | raw_frac);
+}
+
+/* Converts the neutral format floating point number in FP into
+   VAX format with EXP_BITS exponent bits and FRAC_BITS fraction
+   bits, and returns the value. */
+static uint64_t
+assemble_vax (struct fp *fp, int exp_bits, int frac_bits) 
+{
+  const int max_raw_exp = (1 << exp_bits) - 1;
+  const int bias = 1 << (exp_bits - 1);
+  const int min_finite_exp = 1 - bias;
+  const int max_finite_exp = max_raw_exp - bias;
+  const uint64_t max_raw_frac = (UINT64_C(1) << frac_bits) - 1;
+
+  uint64_t raw_frac;
+  int raw_exp;
+  bool raw_sign;
+
+  raw_sign = fp->sign != POSITIVE;
+  
+  switch (fp->class) 
+    {
+    case FINITE: 
+      normalize_and_round_fp (fp, frac_bits + 1);
+      if (fp->exponent > max_finite_exp)
+        {
+          /* Overflow to reserved operand. */
+          raw_sign = 1;
+          raw_exp = 0;
+          raw_frac = 0;
+        }
+      else if (fp->exponent >= min_finite_exp) 
+        {
+          /* Finite. */
+          raw_frac = (fp->fraction << 1) >> (64 - frac_bits);
+          raw_exp = fp->exponent + bias;
+        }
+      else 
+        {
+          /* Underflow to zero. */
+          raw_sign = 0;
+          raw_frac = 0;
+          raw_exp = 0;
+        }
+      break;
+
+    case INFINITE:
+    case NAN:
+    case RESERVED:
+      raw_sign = 1;
+      raw_exp = 0;
+      raw_frac = 0;
+      break;
+
+    case ZERO:
+      raw_sign = 0;
+      raw_frac = 0;
+      raw_exp = 0;
+      break;
+
+    case MISSING:
+      raw_sign = 1;
+      raw_exp = max_finite_exp + bias;
+      raw_frac = max_raw_frac;
+      break;
+
+    case LOWEST:
+      raw_sign = 1;
+      raw_exp = max_finite_exp + bias;
+      raw_frac = max_raw_frac - 1;
+      break;
+
+    case HIGHEST:
+      raw_sign = 0;
+      raw_exp = max_finite_exp + bias;
+      raw_frac = max_raw_frac;
+      break;
+
+    default:
+      NOT_REACHED ();
+    }
+
+  return (((uint64_t) raw_sign << (frac_bits + exp_bits))
+          | ((uint64_t) raw_exp << frac_bits)
+          | raw_frac);
+}
+
+/* Shift right until the exponent is a multiple of 4.
+   Rounding is not needed, because none of the formats we support
+   has more than 53 bits of precision.  That is, we never shift a
+   1-bit off the right end of the fraction.  */
+static void
+normalize_hex_fp (struct fp *fp) 
+{
+  while (fp->exponent % 4) 
+    {
+      fp->fraction >>= 1;
+      fp->exponent++;
+    }
+}
+
+/* Converts the neutral format floating point number in FP into Z
+   architecture format with EXP_BITS exponent bits and FRAC_BITS
+   fraction bits, and returns the value. */
+static uint64_t
+assemble_z (struct fp *fp, int exp_bits, int frac_bits) 
+{
+  const int max_raw_exp = (1 << exp_bits) - 1;
+  const int bias = 1 << (exp_bits - 1);
+  const int max_norm_exp = (max_raw_exp - bias) * 4;
+  const int min_norm_exp = -bias * 4;
+  const int min_denorm_exp = min_norm_exp - (frac_bits - 1);
+
+  const uint64_t max_raw_frac = (UINT64_C(1) << frac_bits) - 1;
+  
+  uint64_t raw_frac;
+  int raw_exp;
+  int raw_sign;
+  
+  raw_sign = fp->sign != POSITIVE;
+  
+  switch (fp->class) 
+    {
+    case FINITE: 
+      normalize_and_round_fp (fp, frac_bits);
+      normalize_hex_fp (fp);
+      if (fp->exponent > max_norm_exp)
+        {
+          /* Overflow to largest value. */
+          raw_exp = max_raw_exp;
+          raw_frac = max_raw_frac;
+        }
+      else if (fp->exponent >= min_norm_exp) 
+        {
+          /* Normal. */
+          raw_frac = fp->fraction >> (64 - frac_bits);
+          raw_exp = (fp->exponent / 4) + bias;
+        }
+      else if (fp->exponent >= min_denorm_exp) 
+        {
+          /* Denormal. */
+          const int denorm_shift = min_norm_exp - fp->exponent;
+          raw_frac = (fp->fraction >> (64 - frac_bits)) >> denorm_shift;
+          raw_exp = 0;
+        }
+      else
+        {
+          /* Underflow to zero. */
+          raw_frac = 0;
+          raw_exp = 0;
+        }
+      break;
+
+    case INFINITE:
+      /* Overflow to largest value. */
+      raw_exp = max_raw_exp;
+      raw_frac = max_raw_frac;
+      break;
+      
+    case NAN:
+    case RESERVED:
+    case ZERO:
+      /* Treat all of these as zero, because Z architecture
+         doesn't have any reserved values. */
+      raw_exp = 0;
+      raw_frac = 0;
+      break;
+
+    case MISSING:
+      raw_sign = 1;
+      raw_exp = max_raw_exp;
+      raw_frac = max_raw_frac;
+      break;
+
+    case LOWEST:
+      raw_sign = 1;
+      raw_exp = max_raw_exp;
+      raw_frac = max_raw_frac - 1;
+      break;
+
+    case HIGHEST:
+      raw_sign = 0;
+      raw_exp = max_raw_exp;
+      raw_frac = max_raw_frac;
+      break;
+
+    default:
+      NOT_REACHED ();
+    }
+
+  return (((uint64_t) raw_sign << (frac_bits + exp_bits))
+          | ((uint64_t) raw_exp << frac_bits)
+          | raw_frac);
+}
+
+/* Converts the neutral format floating point number in FP into a
+   null-terminated human-readable hex string in OUTPUT. */
+static void
+assemble_hex (struct fp *fp, void *output)
+{
+  char buffer[32];
+  char *s = buffer;
+
+  if (fp->sign == NEGATIVE)
+    *s++ = '-';
+  
+  switch (fp->class) 
+    {
+    case FINITE:
+      normalize_and_round_fp (fp, 64);
+      normalize_hex_fp (fp);
+      assert (fp->fraction != 0);
+      
+      *s++ = '.';
+      while (fp->fraction != 0) 
+        {
+          *s++ = (fp->fraction >> 60)["0123456789abcdef"];
+          fp->fraction <<= 4;
+        }
+      if (fp->exponent != 0)
+        sprintf (s, "p%d", fp->exponent);
+      break;
+
+    case INFINITE:
+      strcpy (s, "Infinity");
+      break;
+
+    case NAN:
+      sprintf (s, "NaN:%016"PRIx64, fp->fraction);
+      break;
+
+    case ZERO:
+      strcpy (s, "0");
+      break;
+
+    case MISSING:
+      strcpy (buffer, "Missing");
+      break;
+
+    case LOWEST:
+      strcpy (buffer, "Lowest");
+      break;
+
+    case HIGHEST:
+      strcpy (buffer, "Highest");
+      break;
+
+    case RESERVED:
+      strcpy (s, "Reserved");
+      break;
+    }
+
+  strncpy (output, buffer, float_get_size (FLOAT_HEX));
+}
Index: src/libpspp/float-format.h
===================================================================
RCS file: src/libpspp/float-format.h
diff -N src/libpspp/float-format.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/libpspp/float-format.h	27 Oct 2006 03:23:23 -0000
@@ -0,0 +1,76 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2006 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef LIBPSPP_FLOAT_FORMAT_H
+#define LIBPSPP_FLOAT_FORMAT_H 1
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <libpspp/compiler.h>
+
+/* A floating-point format. */
+enum float_format 
+  {
+    /* IEEE 754 formats. */
+    FLOAT_IEEE_SINGLE_LE,          /* 32 bit, little endian. */
+    FLOAT_IEEE_SINGLE_BE,          /* 32 bit, big endian. */
+    FLOAT_IEEE_DOUBLE_LE,          /* 64 bit, little endian. */
+    FLOAT_IEEE_DOUBLE_BE,          /* 64 bit, big endian. */
+
+    /* VAX formats. */
+    FLOAT_VAX_F,                   /* 32 bit VAX F format. */
+    FLOAT_VAX_D,                   /* 64 bit VAX D format. */
+    FLOAT_VAX_G,                   /* 64 bit VAX G format. */
+
+    /* IBM z architecture (390) hexadecimal formats. */
+    FLOAT_Z_SHORT,                 /* 32 bit format. */
+    FLOAT_Z_LONG,                  /* 64 bit format. */
+
+    /* Formats useful for testing. */
+    FLOAT_FP,                      /* Neutral intermediate format. */
+    FLOAT_HEX,                     /* C99 hexadecimal floating constant. */
+
+#ifdef FPREP_IEEE754
+#ifdef WORDS_BIGENDIAN
+    FLOAT_NATIVE_FLOAT = FLOAT_IEEE_SINGLE_BE,
+    FLOAT_NATIVE_DOUBLE = FLOAT_IEEE_DOUBLE_BE
+    FLOAT_NATIVE_32_BIT = FLOAT_IEEE_SINGLE_BE,
+    FLOAT_NATIVE_64_BIT = FLOAT_IEEE_DOUBLE_BE
+#else
+    FLOAT_NATIVE_FLOAT = FLOAT_IEEE_SINGLE_LE,
+    FLOAT_NATIVE_DOUBLE = FLOAT_IEEE_DOUBLE_LE,
+    FLOAT_NATIVE_32_BIT = FLOAT_IEEE_SINGLE_LE,
+    FLOAT_NATIVE_64_BIT = FLOAT_IEEE_DOUBLE_LE
+#endif
+#else
+#error Only IEEE-754 floating point currently supported for PSPP hosts.
+#endif
+  };
+
+void float_convert (enum float_format, const void *,
+                    enum float_format, void *);
+
+double float_get_double (enum float_format, const void *);
+
+size_t float_get_size (enum float_format) PURE_FUNCTION;
+
+int float_identify (double expected_value, const void *, size_t,
+                    enum float_format *best_guess);
+
+#endif /* float-format.h */
Index: src/libpspp/integer-format.c
===================================================================
RCS file: src/libpspp/integer-format.c
diff -N src/libpspp/integer-format.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/libpspp/integer-format.c	27 Oct 2006 03:23:23 -0000
@@ -0,0 +1,169 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2006 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+
+#include <libpspp/integer-format.h>
+
+#include <assert.h>
+
+/* Returns true if FORMAT is a valid integer format. */
+static inline bool
+is_integer_format (enum integer_format format) 
+{
+  return (format == INTEGER_MSB_FIRST
+          || format == INTEGER_LSB_FIRST
+          || format == INTEGER_VAX);
+}
+
+/* Converts the CNT bytes in INTEGER from SRC integer_format to DST
+   integer_format. */
+void
+integer_convert (enum integer_format src, const void *from, 
+                 enum integer_format dst, void *to,
+                 size_t cnt)
+{
+  if (src != dst) 
+    integer_put (integer_get (src, from, cnt), dst, to, cnt);
+  else if (from != to)
+    memcpy (to, from, cnt);
+}
+
+/* Returns the value of the CNT-byte integer at FROM, which is in
+   the given FORMAT. */
+uint64_t
+integer_get (enum integer_format format, const void *from_, size_t cnt)
+{
+  const uint8_t *from = from_;
+  uint64_t value = 0;
+  size_t i;
+  
+  assert (is_integer_format (format));
+  assert (cnt < 8);
+
+  switch (format) 
+    {
+    case INTEGER_MSB_FIRST:
+      for (i = 0; i < cnt; i++)
+        value = (value << 8) | from[i];
+      break;
+    case INTEGER_LSB_FIRST:
+      for (i = 0; i < cnt; i++)
+        value = (value << 8) | from[cnt - i - 1];
+      break;
+    case INTEGER_VAX:
+      for (i = 0; i < (cnt & ~1); i++)
+        value = (value << 8) | from[i ^ 1];
+      if (cnt & 1)
+        value = (value << 8) | from[cnt - 1];
+      break;
+    }
+
+  return value;
+}
+
+/* Stores VALUE as a CNT-byte integer at TO, in the given
+   FORMAT. */
+void
+integer_put (uint64_t value, enum integer_format format, void *to_, size_t cnt)
+{
+  uint8_t *to = to_;
+  size_t i;
+
+  assert (is_integer_format (format));
+  assert (cnt < 8);
+
+  value <<= 8 * (8 - cnt);
+  
+  switch (format) 
+    {
+    case INTEGER_MSB_FIRST:
+      for (i = 0; i < cnt; i++) 
+        {
+          to[i] = value >> 56;
+          value <<= 8; 
+        }
+      break;
+    case INTEGER_LSB_FIRST:
+      for (i = 0; i < cnt; i++)
+        {
+          to[cnt - i - 1] = value >> 56;
+          value <<= 8;
+        }
+      break;
+    case INTEGER_VAX:
+      for (i = 0; i < (cnt & ~1); i++) 
+        {
+          to[i ^ 1] = value >> 56;
+          value <<= 8; 
+        }
+      if (cnt & 1)
+        to[cnt - 1] = value >> 56;
+      break;
+    }
+}
+
+/* Returns true if bytes with index IDX1 and IDX2 in VALUE differ
+   in value. */
+static inline bool
+bytes_differ (uint64_t value, unsigned int idx1, unsigned int idx2) 
+{
+  uint8_t byte1 = value >> (idx1 * 8);
+  uint8_t byte2 = value >> (idx2 * 8);
+  return byte1 != byte2;
+}
+
+/* Attempts to identify the integer format in which the LENGTH
+   bytes in INTEGER represent the given EXPECTED_VALUE.  Returns
+   true if successful, false otherwise.  On success, stores the
+   format in *FORMAT. */
+bool
+integer_identify (uint64_t expected_value, const void *integer, size_t length,
+                  enum integer_format *format)
+{
+  /* Odd-length integers are confusing. */
+  assert (length % 2 == 0);
+  
+  /* LENGTH must be greater than 2 because VAX format is
+     equivalent to little-endian for 2-byte integers. */
+  assert (length > 2);
+
+  /* EXPECTED_VALUE must contain different byte values, because
+     otherwise all formats are identical. */
+  assert (bytes_differ (expected_value, 0, 1)
+          || bytes_differ (expected_value, 0, 2)
+          || bytes_differ (expected_value, 0, 3)
+          || (length > 4
+              && (bytes_differ (expected_value, 0, 4)
+                  || bytes_differ (expected_value, 0, 5)))
+          || (length > 6
+              && (bytes_differ (expected_value, 0, 6)
+                  || bytes_differ (expected_value, 0, 7))));
+
+  if (integer_get (INTEGER_MSB_FIRST, integer, length) == expected_value)
+    *format = INTEGER_MSB_FIRST;
+  else if (integer_get (INTEGER_LSB_FIRST, integer, length) == expected_value)
+    *format = INTEGER_LSB_FIRST;
+  else if (integer_get (INTEGER_VAX, integer, length) == expected_value)
+    *format = INTEGER_VAX;
+  else
+    return false;
+
+  return true;
+}
Index: src/libpspp/integer-format.h
===================================================================
RCS file: src/libpspp/integer-format.h
diff -N src/libpspp/integer-format.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/libpspp/integer-format.h	27 Oct 2006 03:23:23 -0000
@@ -0,0 +1,53 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2006 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef LIBPSPP_INTEGER_FORMAT_H
+#define LIBPSPP_INTEGER_FORMAT_H 1
+
+#include <byteswap.h>
+#include <stdint.h>
+
+#include <libpspp/str.h>
+
+/* An integer format. */
+enum integer_format
+  {
+    INTEGER_MSB_FIRST,          /* Big-endian: MSB at lowest address. */
+    INTEGER_LSB_FIRST,          /* Little-endian: LSB at lowest address. */
+    INTEGER_VAX,                /* VAX-endian: little-endian 16-bit words
+                                   in big-endian order. */
+
+    /* Native endianness. */
+#ifdef WORDS_BIGENDIAN
+    INTEGER_NATIVE = INTEGER_MSB_FIRST
+#else
+    INTEGER_NATIVE = INTEGER_LSB_FIRST
+#endif
+  };
+
+void integer_convert (enum integer_format, const void *, 
+                      enum integer_format, void *,
+                      size_t);
+uint64_t integer_get (enum integer_format, const void *, size_t);
+void integer_put (uint64_t, enum integer_format, void *, size_t);
+
+bool integer_identify (uint64_t expected_value, const void *, size_t,
+                       enum integer_format *);
+
+#endif /* libpspp/integer-format.h */
Index: src/libpspp/legacy-encoding.c
===================================================================
RCS file: src/libpspp/legacy-encoding.c
diff -N src/libpspp/legacy-encoding.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/libpspp/legacy-encoding.c	27 Oct 2006 03:23:23 -0000
@@ -0,0 +1,135 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2006 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+
+#include <libpspp/legacy-encoding.h>
+
+#include "str.h"
+
+static const char ascii_to_ebcdic[256];
+static const char ebcdic_to_ascii[256];
+
+void
+legacy_recode (enum legacy_encoding from, const char *src,
+             enum legacy_encoding to, char *dst,
+             size_t size) 
+{
+  if (from != to) 
+    {
+      const char *table;
+      size_t i;
+
+      table = from == LEGACY_ASCII ? ascii_to_ebcdic : ebcdic_to_ascii;
+      for (i = 0; i < size; i++)
+        dst[i] = table[(unsigned char) src[i]];
+    }
+  else 
+    {
+      if (src != dst)
+        memcpy (dst, src, size);
+    }
+}
+
+char
+legacy_to_native (enum legacy_encoding from, char c) 
+{
+  legacy_recode (from, &c, LEGACY_NATIVE, &c, 1);
+  return c;
+}
+
+char
+legacy_from_native (enum legacy_encoding to, char c) 
+{
+  legacy_recode (LEGACY_NATIVE, &c, to, &c, 1);
+  return c;
+}
+
+static const char ascii_to_ebcdic[256] = 
+  {
+    0x00, 0x01, 0x02, 0x03, 0x37, 0x2d, 0x2e, 0x2f, 
+    0x16, 0x05, 0x25, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 
+    0x10, 0x11, 0x12, 0x13, 0x3c, 0x3d, 0x32, 0x26, 
+    0x18, 0x19, 0x3f, 0x27, 0x1c, 0x1d, 0x1e, 0x1f, 
+    0x40, 0x5a, 0x7f, 0x7b, 0x5b, 0x6c, 0x50, 0x7d, 
+    0x4d, 0x5d, 0x5c, 0x4e, 0x6b, 0x60, 0x4b, 0x61, 
+    0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 
+    0xf8, 0xf9, 0x7a, 0x5e, 0x4c, 0x7e, 0x6e, 0x6f, 
+    0x7c, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 
+    0xc8, 0xc9, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 
+    0xd7, 0xd8, 0xd9, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 
+    0xe7, 0xe8, 0xe9, 0xad, 0xe0, 0xbd, 0x9a, 0x6d, 
+    0x79, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 
+    0x88, 0x89, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 
+    0x97, 0x98, 0x99, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 
+    0xa7, 0xa8, 0xa9, 0xc0, 0x4f, 0xd0, 0x5f, 0x07, 
+    0x20, 0x21, 0x22, 0x23, 0x24, 0x15, 0x06, 0x17, 
+    0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x09, 0x0a, 0x1b, 
+    0x30, 0x31, 0x1a, 0x33, 0x34, 0x35, 0x36, 0x08, 
+    0x38, 0x39, 0x3a, 0x3b, 0x04, 0x14, 0x3e, 0xe1, 
+    0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 
+    0x49, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 
+    0x58, 0x59, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 
+    0x68, 0x69, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 
+    0x76, 0x77, 0x78, 0x80, 0x8a, 0x8b, 0x8c, 0x8d, 
+    0x8e, 0x8f, 0x90, 0x6a, 0x9b, 0x9c, 0x9d, 0x9e, 
+    0x9f, 0xa0, 0xaa, 0xab, 0xac, 0x4a, 0xae, 0xaf, 
+    0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 
+    0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xa1, 0xbe, 0xbf, 
+    0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xda, 0xdb, 
+    0xdc, 0xdd, 0xde, 0xdf, 0xea, 0xeb, 0xec, 0xed, 
+    0xee, 0xef, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 
+  };
+
+static const char ebcdic_to_ascii[256] = 
+  {
+    0x00, 0x01, 0x02, 0x03, 0x9c, 0x09, 0x86, 0x7f, 
+    0x97, 0x8d, 0x8e, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 
+    0x10, 0x11, 0x12, 0x13, 0x9d, 0x85, 0x08, 0x87, 
+    0x18, 0x19, 0x92, 0x8f, 0x1c, 0x1d, 0x1e, 0x1f, 
+    0x80, 0x81, 0x82, 0x83, 0x84, 0x0a, 0x17, 0x1b, 
+    0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x05, 0x06, 0x07, 
+    0x90, 0x91, 0x16, 0x93, 0x94, 0x95, 0x96, 0x04, 
+    0x98, 0x99, 0x9a, 0x9b, 0x14, 0x15, 0x9e, 0x1a, 
+    0x20, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 
+    0xa7, 0xa8, 0xd5, 0x2e, 0x3c, 0x28, 0x2b, 0x7c, 
+    0x26, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 
+    0xb0, 0xb1, 0x21, 0x24, 0x2a, 0x29, 0x3b, 0x7e, 
+    0x2d, 0x2f, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 
+    0xb8, 0xb9, 0xcb, 0x2c, 0x25, 0x5f, 0x3e, 0x3f, 
+    0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 
+    0xc2, 0x60, 0x3a, 0x23, 0x40, 0x27, 0x3d, 0x22, 
+    0xc3, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 
+    0x68, 0x69, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 
+    0xca, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 
+    0x71, 0x72, 0x5e, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 
+    0xd1, 0xe5, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 
+    0x79, 0x7a, 0xd2, 0xd3, 0xd4, 0x5b, 0xd6, 0xd7, 
+    0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 
+    0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0x5d, 0xe6, 0xe7, 
+    0x7b, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 
+    0x48, 0x49, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 
+    0x7d, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 
+    0x51, 0x52, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 
+    0x5c, 0x9f, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 
+    0x59, 0x5a, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 
+    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 
+    0x38, 0x39, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 
+  };
+
Index: src/libpspp/legacy-encoding.h
===================================================================
RCS file: src/libpspp/legacy-encoding.h
diff -N src/libpspp/legacy-encoding.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/libpspp/legacy-encoding.h	27 Oct 2006 03:23:23 -0000
@@ -0,0 +1,49 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2006 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef LIBPSPP_LEGACY_ENCODING
+#define LIBPSPP_LEGACY_ENCODING 1
+
+#include <stddef.h>
+#include <libpspp/compiler.h>
+
+/* A legacy character encoding.
+   This exists only to handle the specific legacy EBCDIC-to-ASCII
+   recoding that MODE=360 file handles perform. */
+enum legacy_encoding
+  {
+    LEGACY_ASCII,         /* ASCII or similar character set. */
+    LEGACY_EBCDIC,        /* IBM EBCDIC character set. */
+
+    /* Native character set. */
+#if 'A' == 0x41
+    LEGACY_NATIVE = LEGACY_ASCII
+#elif 'A' == 0xc1
+    LEGACY_NATIVE = LEGACY_EBCDIC
+#else
+#error Cannot detect native character set.
+#endif
+  };
+
+void legacy_recode (enum legacy_encoding, const char *src,
+                    enum legacy_encoding, char *dst, size_t);
+char legacy_to_native (enum legacy_encoding from, char) PURE_FUNCTION;
+char legacy_from_native (enum legacy_encoding to, char) PURE_FUNCTION;
+
+#endif /* libpspp/legacy-encoding.h */
Index: tests/ChangeLog
===================================================================
RCS file: /cvsroot/pspp/pspp/tests/ChangeLog,v
retrieving revision 1.62
diff -u -p -r1.62 ChangeLog
--- tests/ChangeLog	7 Oct 2006 03:10:14 -0000	1.62
+++ tests/ChangeLog	27 Oct 2006 03:23:24 -0000
@@ -1,3 +1,9 @@
+Thu Oct 26 20:20:39 2006  Ben Pfaff  <blp@gnu.org>
+
+	* automake.mk: Add tests/formats/float-format.sh.
+
+	* tests/formats/float-format.sh: New test.
+
 Sat Oct  7 11:06:59 WST 2006 John Darrington <john@darrington.wattle.id.au>
 
 	* command/rank.sh: New file 
Index: tests/automake.mk
===================================================================
RCS file: /cvsroot/pspp/pspp/tests/automake.mk,v
retrieving revision 1.13
diff -u -p -r1.13 automake.mk
--- tests/automake.mk	7 Oct 2006 03:10:14 -0000	1.13
+++ tests/automake.mk	27 Oct 2006 03:23:24 -0000
@@ -57,6 +57,7 @@ TESTS = \
 	tests/command/use.sh \
 	tests/command/very-long-strings.sh \
 	tests/command/weight.sh \
+	tests/formats/float-format.sh \
 	tests/bugs/agg_crash.sh \
 	tests/bugs/agg-crash-2.sh \
 	tests/bugs/alpha-freq.sh \
Index: tests/formats/float-format.sh
===================================================================
RCS file: tests/formats/float-format.sh
diff -N tests/formats/float-format.sh
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/formats/float-format.sh	27 Oct 2006 03:23:24 -0000
@@ -0,0 +1,154 @@
+#! /bin/sh
+
+# Tests floating-point format conversions.
+
+TEMPDIR=/tmp/pspp-tst-$$
+
+# ensure that top_builddir  are absolute
+if [ -z "$top_builddir" ] ; then top_builddir=. ; fi
+if [ -z "$top_srcdir" ] ; then top_srcdir=. ; fi
+top_builddir=`cd $top_builddir; pwd`
+PSPP=$top_builddir/src/ui/terminal/pspp
+
+# ensure that top_srcdir is absolute
+top_srcdir=`cd $top_srcdir; pwd`
+
+STAT_CONFIG_PATH=$top_srcdir/config
+export STAT_CONFIG_PATH
+
+
+cleanup()
+{
+     cd /
+     rm -rf $TEMPDIR
+}
+
+
+fail()
+{
+    echo $activity
+    echo FAILED
+    cleanup;
+    exit 1;
+}
+
+
+no_result()
+{
+    echo $activity
+    echo NO RESULT;
+    cleanup;
+    exit 2;
+}
+
+pass()
+{
+    cleanup;
+    exit 0;
+}
+
+mkdir -p $TEMPDIR
+
+cd $TEMPDIR
+activity="create test program"
+sed -n \
+    -e 's/#.*//' \
+    -e 's/^[ 	]*//' \
+    -e 's/[ 	]*$//' \
+    -e '/^$/d' \
+    -e 's/^\(.*\)$/DEBUG FLOAT FORMAT \1./p' \
+    > $TEMPDIR/float-format.pspp <<'EOF'
+# Each of the tests below checks that conversion between
+# floating-point formats works correctly.  Comparisons that use ==
+# require that conversion from any format on the line to any other
+# format on the line work losslessly.  Comparisons that use => only
+# check that conversions work losslessly in the given direction.
+
+# Key to format names:
+# isl: IEEE single-precision, little endian
+# isb: IEEE single-precision, big endian
+# idl: IEEE double-precision, little endian
+# idb: IEEE double-precision, big endian
+# vf: VAX F
+# vd: VAX D
+# vg: VAX G
+# zs: Z architecture short
+# zl: Z architecture long
+# x: hexadecimal digits
+
+# IEEE special values.
+ 0 == isb(x'00000000')
+-0 == isb(x'80000000')
+x('Infinity') == isb(x'7f800000')
+x('-Infinity') == isb(x'ff800000')
+x('NaN:') => isb(x'7f800001')		# NaN requires nonzero fraction.
+x('NaN:e000000000000000') == isb(x'7ff00000') == idb(x'7ffe000000000000')
+x('NaN:5a5a5e0000000000') == isb(x'7fad2d2f') == idb(x'7ff5a5a5e0000000')
+x('NaN:975612abcdef4000') == idb(x'7ff975612abcdef4')
+x('-NaN:e000000000000000') == isb(x'fff00000') == idb(x'fffe000000000000')
+x('-NaN:5a5a5e0000000000') == isb(x'ffad2d2f') == idb(x'fff5a5a5e0000000')
+x('-NaN:975612abcdef4000') == idb(x'fff975612abcdef4')
+
+# PSPP special values.
+x('Missing') == isb(x'ff7fffff') == idb(x'ffefffffffffffff') == isl(x'ffff7fff') == idl(x'ffffffffffffefff') == vf(x'ffffffff') == vd(x'ffffffffffffffff') == vg(x'ffffffffffffffff') == zs(x'ffffffff') == zl(x'ffffffffffffffff')
+x('Lowest') == isb(x'ff7ffffe') == idb(x'ffeffffffffffffe') == isl(x'feff7fff') == idl(x'feffffffffffefff') == vf(x'fffffeff') == vd(x'fffffeffffffffff') == vg(x'fffffeffffffffff') == zs(x'fffffffe') == zl(x'fffffffffffffffe')
+x('Highest') == isb(x'7f7fffff') == idb(x'7fefffffffffffff') == isl(x'ffff7f7f') == idl(x'ffffffffffffef7f') == vf(x'ff7fffff') == vd(x'ffffffffff7fffff') == vg(x'ffffffffff7fffff') == zs(x'7fffffff') == zl(x'7fffffffffffffff')
+
+# From Wikipedia.
+0.15625 == isb(b'00111110001000000000000000000000')
+-118.625 == isb(b'11000010111011010100000000000000')
+
+# http://www.psc.edu/general/software/packages/ieee/ieee.html
+x('NaN:0400000000000000') == isb(b'01111111100000100000000000000000')
+x('-NaN:2225540000000000') == isb(b'11111111100100010001001010101010')
+2 == isb(b'01000000000000000000000000000000')
+6.5 == isb(b'01000000110100000000000000000000')
+-6.5 == isb(b'11000000110100000000000000000000')
+x('.4p-124') == isb(b'00000000100000000000000000000000')
+x('.2p-124') == isb(b'00000000010000000000000000000000')
+
+# Using converter at http://babbage.cs.qc.edu/IEEE-754/Decimal.html
+# plus Emacs 'calc' to convert decimal to hexadecimal
+x('.7b74bc6a7ef9db23p8') => isb(x'42f6e979')		# 123.456
+x('.7b74bc6a7ef9db23p8') => idb(x'405edd2f1a9fbe77')
+x('.817427d2d4642004p-12') => isb(x'39017428')		# .0001234567
+x('.817427d2d4642004p-12') => idb(x'3f202e84fa5a8c84')
+x('.446c3b15f9926688p168') => isb(x'7f800000')		# 1e50; overflow
+x('.446c3b15f9926688p168') => idb(x'4a511b0ec57e649a')
+
+# From multiple editions of the z/Architecture Principles of Operation
+# manual.
+	      1.0 == zs(x'41100000') == isb(x'3f800000')
+	      0.5 == zs(x'40800000') == isb(x'3f000000')
+       x('.4p-4') == zs(x'3f400000') == isb(x'3c800000')
+		0 == zs(x'00000000') == isb(x'00000000')
+	       -0 == zs(x'80000000') == isb(x'80000000')
+	      -15 == zs(x'c1f00000') == isb(x'c1700000')
+# x('.ffffffp252') == zs(x'7fffffff')
+      x('.3b4p8') == zs(x'423b4000')
+     x('.1p-256') == zs(x'00100000')
+     x('.4p-124') == zs(x'21400000') == isb(x'00800000')
+     x('.8p-148') == zs(x'1b800000') == isb(x'00000001')
+# x('.ffffffp128') == zs(x'60ffffff') == isb(x'7f7fffff')
+     x('.1p-256') == zs(x'00100000')
+     x('.1p-256') => isb(x'00000000')              # Underflow to zero.
+ x('.ffffffp248') == zs(x'7effffff')
+ x('.ffffffp248') => isb(x'7f800000')              # Overflow to +Infinity.
+
+            x('.4p-1020') => zl(x'0000000000000000')     # Underflow to zero.
+            x('.4p-1020') == idb(x'0010000000000000')
+            x('.4p-1072') => zl(x'0000000000000000')     # Underflow to zero.
+            x('.4p-1072') => idb(x'0000000000000001')
+x('.fffffffffffff8p1024') => zl(x'7fffffffffffffff')     # Overflow to maxval.
+x('.fffffffffffff8p1024') => idb(x'7fefffffffffffff')
+            x('.1p-256') == zl(x'0010000000000000') == idb(x'2fb0000000000000')
+ x('.ffffffffffffffp248') == zl(x'7effffffffffffff')
+ x('.ffffffffffffffp248') => idb(x'4f70000000000000')	# Loses precision.
+EOF
+if [ $? -ne 0 ] ; then no_result ; fi
+
+activity="run program"
+$SUPERVISOR $PSPP --testing-mode -o raw-ascii $TEMPDIR/float-format.pspp
+if [ $? -ne 0 ] ; then fail ; fi
+
+pass
