5df6e063fbec9e956ed58c431d6af6f70fa8fc76
[pspp] / src / language / tests / float-format.c
1 /* PSPP - computes sample statistics.
2    Copyright (C) 2006 Free Software Foundation, Inc.
3
4    This program is free software; you can redistribute it and/or
5    modify it under the terms of the GNU General Public License as
6    published by the Free Software Foundation; either version 2 of the
7    License, or (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful, but
10    WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software
16    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17    02110-1301, USA. */
18
19 #include <config.h>
20
21 #include <libpspp/float-format.h>
22
23 #include "gettext.h"
24 #include <inttypes.h>
25
26 #include <language/command.h>
27 #include <language/lexer/lexer.h>
28 #include <libpspp/assertion.h>
29 #include <libpspp/message.h>
30 #include <libpspp/str.h>
31
32 #define _(msgid) gettext (msgid)
33
34 /* Maximum supported size of a floating-point number, in bytes. */
35 #define FP_MAX_SIZE 32
36
37 /* A floating-point number tagged with its representation. */
38 struct fp
39   {
40     enum float_format format;           /* Format. */
41     uint8_t data[FP_MAX_SIZE];          /* Representation. */
42   };
43
44 /* Associates a format name with its identifier. */
45 struct assoc
46   {
47     char name[4];
48     enum float_format format;
49   };
50
51 /* List of floating-point formats. */
52 static const struct assoc fp_formats[] =
53   {
54     {"ISL", FLOAT_IEEE_SINGLE_LE},
55     {"ISB", FLOAT_IEEE_SINGLE_BE},
56     {"IDL", FLOAT_IEEE_DOUBLE_LE},
57     {"IDB", FLOAT_IEEE_DOUBLE_BE},
58     {"VF", FLOAT_VAX_F},
59     {"VD", FLOAT_VAX_D},
60     {"VG", FLOAT_VAX_G},
61     {"ZS", FLOAT_Z_SHORT},
62     {"ZL", FLOAT_Z_LONG},
63     {"X", FLOAT_HEX},
64     {"FP", FLOAT_FP},
65   };
66 static const size_t format_cnt = sizeof fp_formats / sizeof *fp_formats;
67
68 /* Parses a floating-point format name into *FORMAT,
69    and returns success. */
70 static bool
71 parse_float_format (struct lexer *lexer, enum float_format *format)
72 {
73   size_t i;
74
75   for (i = 0; i < format_cnt; i++)
76     if (lex_match_id (lexer, fp_formats[i].name))
77       {
78         *format = fp_formats[i].format;
79         return true;
80       }
81   lex_error (lexer, "expecting floating-point format identifier");
82   return false;
83 }
84
85 /* Returns the name for the given FORMAT. */
86 static const char *
87 get_float_format_name (enum float_format format)
88 {
89   size_t i;
90
91   for (i = 0; i < format_cnt; i++)
92     if (fp_formats[i].format == format)
93       return fp_formats[i].name;
94
95   NOT_REACHED ();
96 }
97
98 /* Parses a number in the form FORMAT(STRING), where FORMAT is
99    the name of the format and STRING gives the number's
100    representation.  Also supports ordinary floating-point numbers
101    written in decimal notation.  Returns success. */
102 static bool
103 parse_fp (struct lexer *lexer, struct fp *fp)
104 {
105   if (lex_is_number (lexer))
106     {
107       double number = lex_number (lexer);
108       fp->format = FLOAT_NATIVE_DOUBLE;
109       memcpy (fp->data, &number, sizeof number);
110       lex_get (lexer);
111     }
112   else if (lex_token (lexer) == T_ID)
113     {
114       size_t length;
115
116       if (!parse_float_format (lexer, &fp->format)
117           || !lex_force_match (lexer, '(')
118           || !lex_force_string (lexer))
119         return false;
120
121       length = ds_length (lex_tokstr (lexer));
122       if (fp->format != FLOAT_HEX)
123         {
124           if (length != float_get_size (fp->format))
125             {
126               msg (SE, _("%d-byte string needed but %d-byte string supplied."),
127                    (int) float_get_size (fp->format), (int) length);
128               return false;
129             }
130           assert (length <= sizeof fp->data);
131           memcpy (fp->data, ds_data (lex_tokstr (lexer)), length);
132         }
133       else
134         {
135           if (length >= sizeof fp->data)
136             {
137               msg (SE, _("Hexadecimal floating constant too long."));
138               return false;
139             }
140           strncpy ((char *) fp->data, ds_cstr (lex_tokstr (lexer)), sizeof fp->data);
141         }
142
143       lex_get (lexer);
144       if (!lex_force_match (lexer, ')'))
145         return false;
146     }
147   else
148     {
149       lex_error (lexer, NULL);
150       return false;
151     }
152   return true;
153 }
154
155 /* Renders SRC, which contains SRC_SIZE bytes of a floating-point
156    number in the given FORMAT, as relatively human-readable
157    null-terminated string in the DST_SIZE bytes in DST.  DST_SIZE
158    must be be large enough to hold the output. */
159 static void
160 make_printable (enum float_format format, const void *src_, size_t src_size,
161                 char *dst, size_t dst_size)
162 {
163   assert (dst_size >= 2 * src_size + 1);
164   if (format != FLOAT_HEX)
165     {
166       const uint8_t *src = src_;
167       while (src_size-- > 0)
168         {
169           sprintf (dst, "%02x", *src++);
170           dst += 2;
171         }
172       *dst = '\0';
173     }
174   else
175     strncpy (dst, src_, src_size + 1);
176 }
177
178 /* Checks that RESULT is identical to TO.
179    If so, returns false.
180    If not, issues a helpful error message that includes the given
181    CONVERSION_TYPE and the value that was converted FROM, and
182    returns true. */
183 static bool
184 mismatch (const struct fp *from, const struct fp *to, char *result,
185           const char *conversion_type)
186 {
187   size_t to_size = float_get_size (to->format);
188   if (!memcmp (to->data, result, to_size))
189     return false;
190   else
191     {
192       size_t from_size = float_get_size (from->format);
193       char original[FP_MAX_SIZE * 2 + 1];
194       char expected[FP_MAX_SIZE * 2 + 1];
195       char actual[FP_MAX_SIZE * 2 + 1];
196       make_printable (from->format, from->data, from_size, original,
197                       sizeof original);
198       make_printable (to->format, to->data, to_size, expected,
199                       sizeof expected);
200       make_printable (to->format, result, to_size, actual, sizeof actual);
201       msg (SE,
202            _("%s conversion of %s from %s to %s should have produced %s "
203              "but actually produced %s."),
204            conversion_type,
205            original, get_float_format_name (from->format),
206            get_float_format_name (to->format), expected,
207            actual);
208       return true;
209     }
210 }
211
212 /* Checks that converting FROM into the format of TO yields
213    exactly the data in TO. */
214 static bool
215 verify_conversion (const struct fp *from, const struct fp *to)
216 {
217   char tmp1[FP_MAX_SIZE], tmp2[FP_MAX_SIZE];
218
219   /* First try converting directly. */
220   float_convert (from->format, from->data, to->format, tmp1);
221   if (mismatch (from, to, tmp1, "Direct"))
222     return false;
223
224   /* Then convert via FLOAT_FP to prevent short-circuiting that
225      float_convert() does for some conversions (e.g. little<->big
226      endian for IEEE formats). */
227   float_convert (from->format, from->data, FLOAT_FP, tmp1);
228   float_convert (FLOAT_FP, tmp1, to->format, tmp2);
229   if (mismatch (from, to, tmp2, "Indirect"))
230     return false;
231
232   return true;
233 }
234
235 /* Executes the DEBUG FLOAT FORMAT command. */
236 int
237 cmd_debug_float_format (struct lexer *lexer, struct dataset *ds UNUSED)
238 {
239   struct fp fp[16];
240   size_t fp_cnt = 0;
241   bool bijective = false;
242   bool ok;
243
244   for (;;)
245     {
246       if (fp_cnt >= sizeof fp / sizeof *fp)
247         {
248           msg (SE, _("Too many values in single command."));
249           return CMD_FAILURE;
250         }
251       if (!parse_fp (lexer, &fp[fp_cnt++]))
252         return CMD_FAILURE;
253
254       if (lex_token (lexer) == '.' && fp_cnt > 1)
255         break;
256       else if (!lex_force_match (lexer, '='))
257         return CMD_FAILURE;
258
259       if (fp_cnt == 1)
260         {
261           if (lex_match (lexer, '='))
262             bijective = true;
263           else if (lex_match (lexer, T_GT))
264             bijective = false;
265           else
266             {
267               lex_error (lexer, NULL);
268               return CMD_FAILURE;
269             }
270         }
271       else
272         {
273           if ((bijective && !lex_force_match (lexer, '='))
274               || (!bijective && !lex_force_match (lexer, T_GT)))
275             return CMD_FAILURE;
276         }
277     }
278
279   ok = true;
280   if (bijective)
281     {
282       size_t i, j;
283
284       for (i = 0; i < fp_cnt; i++)
285         for (j = 0; j < fp_cnt; j++)
286           if (!verify_conversion (&fp[i], &fp[j]))
287             ok = false;
288     }
289   else
290     {
291       size_t i;
292
293       for (i = 1; i < fp_cnt; i++)
294         if (!verify_conversion (&fp[i - 1], &fp[i]))
295           ok = false;
296     }
297
298   return ok ? CMD_SUCCESS : CMD_FAILURE;
299 }