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