#include "error.h"
#include <ctype.h>
#include <errno.h>
+#include <fcntl.h>
#include <float.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
+#include <sys/stat.h>
#include <time.h>
+#include <unistd.h>
#include "alloc.h"
#include "case.h"
#include "dictionary.h"
#include "hash.h"
#include "magic.h"
#include "misc.h"
+#include "stat-macros.h"
#include "str.h"
#include "value-labels.h"
#include "var.h"
#include "version.h"
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
#include "debug-print.h"
/* Portable file writer. */
size_t var_cnt; /* Number of variables. */
struct pfm_var *vars; /* Variables. */
+
+ int digits; /* Digits of precision. */
};
/* A variable to write to the portable file. */
static void format_trig_double (long double, int base_10_precision, char[]);
static char *format_trig_int (int, bool force_sign, char[]);
-/* Writes the dictionary DICT to portable file HANDLE. Returns
- nonzero only if successful. DICT will not be modified, except
- to assign short names. */
+/* Returns default options for writing a portable file. */
+struct pfm_write_options
+pfm_writer_default_options (void)
+{
+ struct pfm_write_options opts;
+ opts.create_writeable = true;
+ opts.type = PFM_COMM;
+ opts.digits = DBL_DIG;
+ return opts;
+}
+
+/* Writes the dictionary DICT to portable file HANDLE according
+ to the given OPTS. Returns nonzero only if successful. DICT
+ will not be modified, except to assign short names. */
struct pfm_writer *
-pfm_open_writer (struct file_handle *fh, struct dictionary *dict)
+pfm_open_writer (struct file_handle *fh, struct dictionary *dict,
+ struct pfm_write_options opts)
{
struct pfm_writer *w = NULL;
+ mode_t mode;
+ int fd;
size_t i;
+ /* Create file. */
+ mode = S_IRUSR | S_IRGRP | S_IROTH;
+ if (opts.create_writeable)
+ mode |= S_IWUSR | S_IWGRP | S_IWOTH;
+ fd = open (handle_get_filename (fh), O_WRONLY | O_CREAT | O_TRUNC, mode);
+ if (fd < 0)
+ goto open_error;
+
+ /* Open file handle. */
if (!fh_open (fh, "portable file", "we"))
goto error;
-
- /* Open the physical disk file. */
+
+ /* Initialize data structures. */
w = xmalloc (sizeof *w);
w->fh = fh;
- w->file = fopen (handle_get_filename (fh), "wb");
+ w->file = fdopen (fd, "w");
+ if (w->file == NULL)
+ {
+ close (fd);
+ goto open_error;
+ }
+
w->lc = 0;
w->var_cnt = 0;
w->vars = NULL;
- /* Check that file create succeeded. */
- if (w->file == NULL)
- {
- msg (ME, _("An error occurred while opening \"%s\" for writing "
- "as a portable file: %s."),
- handle_get_filename (fh), strerror (errno));
- err_cond_fail ();
- goto error;
- }
-
w->var_cnt = dict_get_var_cnt (dict);
w->vars = xmalloc (sizeof *w->vars * w->var_cnt);
for (i = 0; i < w->var_cnt; i++)
pv->fv = dv->fv;
}
+ w->digits = opts.digits;
+ if (w->digits < 1)
+ {
+ msg (ME, _("Invalid decimal digits count %d. Treating as %d."),
+ w->digits, DBL_DIG);
+ w->digits = DBL_DIG;
+ }
+
/* Write file header. */
if (!write_header (w)
|| !write_version_data (w)
return w;
-error:
+ error:
pfm_close_writer (w);
return NULL;
+
+ open_error:
+ msg (ME, _("An error occurred while opening \"%s\" for writing "
+ "as a portable file: %s."),
+ handle_get_filename (fh), strerror (errno));
+ err_cond_fail ();
+ goto error;
}
\f
/* Write NBYTES starting at BUF to the portable file represented by
write_float (struct pfm_writer *w, double d)
{
char buffer[64];
- format_trig_double (d, DBL_DIG, buffer);
+ format_trig_double (d, floor (d) == d ? DBL_DIG : w->digits, buffer);
return buf_write (w, buffer, strlen (buffer)) && buf_write (w, "/", 1);
}
for (i = 0; i < dict_get_var_cnt (dict); i++)
{
- static const char *miss_types[MISSING_COUNT] =
- {
- "", "8", "88", "888", "B ", "9", "A", "B 8", "98", "A8",
- };
-
- const char *m;
- int j;
-
struct variable *v = dict_get_var (dict, i);
+ struct missing_values mv;
if (!buf_write (w, "7", 1) || !write_int (w, v->width)
|| !write_string (w, v->short_name)
|| !write_format (w, &v->print) || !write_format (w, &v->write))
return 0;
- for (m = miss_types[v->miss_type], j = 0; j < (int) strlen (m); j++)
- if ((m[j] != ' ' && !buf_write (w, &m[j], 1))
- || !write_value (w, &v->missing[j], v))
- return 0;
+ /* Write missing values. */
+ mv_copy (&mv, &v->miss);
+ while (mv_has_range (&mv))
+ {
+ double x, y;
+ mv_pop_range (&mv, &x, &y);
+ if (x == LOWEST)
+ {
+ if (!buf_write (w, "9", 1) || !write_float (w, y))
+ return 0;
+ }
+ else if (y == HIGHEST)
+ {
+ if (!buf_write (w, "A", 1) || !write_float (w, y))
+ return 0;
+ }
+ else {
+ if (!buf_write (w, "B", 1) || !write_float (w, x)
+ || !write_float (w, y))
+ return 0;
+ }
+ }
+ while (mv_has_value (&mv))
+ {
+ union value value;
+ mv_pop_value (&mv, &value);
+ if (!buf_write (w, "8", 1) || !write_value (w, &value, v))
+ return 0;
+ }
if (v->label && (!buf_write (w, "C", 1) || !write_string (w, v->label)))
return 0;
free (w);
}
\f
-/* Base-30 conversion. */
+/* Base-30 conversion.
+
+ Portable files represent numbers in base-30 format, so we need
+ to be able to convert real and integer number to that base.
+ Older versions of PSPP used libgmp to do so, but this added a
+ big library dependency to do just one thing. Now we do it
+ ourselves internally.
+
+ Important fact: base 30 is called "trigesimal". */
/* Conversion base. */
#define BASE 30 /* As an integer. */
digits that a `long int' can hold. */
#define CHUNK_SIZE 6
-/* Yields the square of X. */
-#define Q(X) ((X) * (X))
+/* pow_tab[i] = pow (30, pow (2, i)) */
+static long double pow_tab[16];
+
+/* Initializes pow_tab[]. */
+static void
+init_pow_tab (void)
+{
+ static bool did_init = false;
+ long double power;
+ size_t i;
+
+ /* Only initialize once. */
+ if (did_init)
+ return;
+ did_init = true;
+
+ /* Set each element of pow_tab[] until we run out of numerical
+ range. */
+ i = 0;
+ for (power = 30.0L; power < DBL_MAX; power *= power)
+ {
+ assert (i < sizeof pow_tab / sizeof *pow_tab);
+ pow_tab[i++] = power;
+ }
+}
/* Returns 30**EXPONENT, for 0 <= EXPONENT <= log30(DBL_MAX). */
static long double
pow30_nonnegative (int exponent)
{
- /* pow_tab[i] = pow (30, pow (2, i)) */
- static const long double pow_tab[] =
- {
- LDBASE,
- Q (LDBASE),
- Q (Q (LDBASE)),
- Q (Q (Q (LDBASE))),
- Q (Q (Q (Q (LDBASE)))),
- Q (Q (Q (Q (Q (LDBASE))))),
- Q (Q (Q (Q (Q (Q (LDBASE)))))),
- Q (Q (Q (Q (Q (Q (Q (LDBASE))))))),
- Q (Q (Q (Q (Q (Q (Q (Q (LDBASE)))))))),
- Q (Q (Q (Q (Q (Q (Q (Q (Q (LDBASE))))))))),
- Q (Q (Q (Q (Q (Q (Q (Q (Q (Q (LDBASE)))))))))),
- Q (Q (Q (Q (Q (Q (Q (Q (Q (Q (Q (LDBASE))))))))))),
- };
-
long double power;
int i;
/* Number of trigesimal places left to write into BUFFER. */
int trigs_to_output;
+ init_pow_tab ();
+
/* Handle special cases. */
if (value == SYSMIS)
goto missing_value;
required base-30 precision as 2/3 of the base-10 precision
(log30(10) = .68). */
assert (base_10_precision > 0);
+ if (base_10_precision > LDBL_DIG)
+ base_10_precision = LDBL_DIG;
base_30_precision = DIV_RND_UP (base_10_precision * 2, 3);
if (trig_cnt > base_30_precision)
{