817246bc8167dbcd0df48fcca125e951b109c320
[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 "gettext.h"
22 #include <inttypes.h>
23
24 #include <language/command.h>
25 #include <language/lexer/lexer.h>
26 #include <libpspp/assertion.h>
27 #include <libpspp/message.h>
28 #include <libpspp/str.h>
29
30 #define _(msgid) gettext (msgid)
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 /* Parses a number in the form FORMAT(STRING), where FORMAT is
97    the name of the format and STRING gives the number's
98    representation.  Also supports ordinary floating-point numbers
99    written in decimal notation.  Returns success. */
100 static bool
101 parse_fp (struct lexer *lexer, struct fp *fp)
102 {
103   if (lex_is_number (lexer))
104     {
105       double number = lex_number (lexer);
106       fp->format = FLOAT_NATIVE_DOUBLE;
107       memcpy (fp->data, &number, sizeof number);
108       lex_get (lexer);
109     }
110   else if (lex_token (lexer) == T_ID)
111     {
112       size_t length;
113
114       if (!parse_float_format (lexer, &fp->format)
115           || !lex_force_match (lexer, T_LPAREN)
116           || !lex_force_string (lexer))
117         return false;
118
119       length = ss_length (lex_tokss (lexer));
120       if (fp->format != FLOAT_HEX)
121         {
122           if (length != float_get_size (fp->format))
123             {
124               msg (SE, _("%zu-byte string needed but %zu-byte string "
125                          "supplied."),
126                    float_get_size (fp->format), length);
127               return false;
128             }
129           assert (length <= sizeof fp->data);
130           memcpy (fp->data, ss_data (lex_tokss (lexer)), length);
131         }
132       else
133         {
134           if (length >= sizeof fp->data)
135             {
136               msg (SE, _("Hexadecimal floating constant too long."));
137               return false;
138             }
139           strncpy (CHAR_CAST_BUG (char *,fp->data), lex_tokcstr (lexer), sizeof fp->data);
140         }
141
142       lex_get (lexer);
143       if (!lex_force_match (lexer, T_RPAREN))
144         return false;
145     }
146   else
147     {
148       lex_error (lexer, NULL);
149       return false;
150     }
151   return true;
152 }
153
154 /* Renders SRC, which contains SRC_SIZE bytes of a floating-point
155    number in the given FORMAT, as relatively human-readable
156    null-terminated string in the DST_SIZE bytes in DST.  DST_SIZE
157    must be be large enough to hold the output. */
158 static void
159 make_printable (enum float_format format, const void *src_, size_t src_size,
160                 char *dst, size_t dst_size)
161 {
162   assert (dst_size >= 2 * src_size + 1);
163   if (format != FLOAT_HEX)
164     {
165       const uint8_t *src = src_;
166       while (src_size-- > 0)
167         {
168           sprintf (dst, "%02x", *src++);
169           dst += 2;
170         }
171       *dst = '\0';
172     }
173   else
174     strncpy (dst, src_, src_size + 1);
175 }
176
177 /* Checks that RESULT is identical to TO.
178    If so, returns false.
179    If not, issues a helpful error message that includes the given
180    CONVERSION_TYPE and the value that was converted FROM, and
181    returns true. */
182 static bool
183 mismatch (const struct fp *from, const struct fp *to, char *result,
184           const char *conversion_type)
185 {
186   size_t to_size = float_get_size (to->format);
187   if (!memcmp (to->data, result, to_size))
188     return false;
189   else
190     {
191       size_t from_size = float_get_size (from->format);
192       char original[FP_MAX_SIZE * 2 + 1];
193       char expected[FP_MAX_SIZE * 2 + 1];
194       char actual[FP_MAX_SIZE * 2 + 1];
195       make_printable (from->format, from->data, from_size, original,
196                       sizeof original);
197       make_printable (to->format, to->data, to_size, expected,
198                       sizeof expected);
199       make_printable (to->format, result, to_size, actual, sizeof actual);
200       msg (SE, "%s conversion of %s from %s to %s should have produced %s "
201            "but actually produced %s.",
202            conversion_type,
203            original, get_float_format_name (from->format),
204            get_float_format_name (to->format), expected,
205            actual);
206       return true;
207     }
208 }
209
210 /* Checks that converting FROM into the format of TO yields
211    exactly the data in TO. */
212 static bool
213 verify_conversion (const struct fp *from, const struct fp *to)
214 {
215   char tmp1[FP_MAX_SIZE], tmp2[FP_MAX_SIZE];
216
217   /* First try converting directly. */
218   float_convert (from->format, from->data, to->format, tmp1);
219   if (mismatch (from, to, tmp1, "Direct"))
220     return false;
221
222   /* Then convert via FLOAT_FP to prevent short-circuiting that
223      float_convert() does for some conversions (e.g. little<->big
224      endian for IEEE formats). */
225   float_convert (from->format, from->data, FLOAT_FP, tmp1);
226   float_convert (FLOAT_FP, tmp1, to->format, tmp2);
227   if (mismatch (from, to, tmp2, "Indirect"))
228     return false;
229
230   return true;
231 }
232
233 /* Executes the DEBUG FLOAT FORMAT command. */
234 int
235 cmd_debug_float_format (struct lexer *lexer, struct dataset *ds UNUSED)
236 {
237   struct fp fp[16];
238   size_t fp_cnt = 0;
239   bool bijective = false;
240   bool ok;
241
242   for (;;)
243     {
244       if (fp_cnt >= sizeof fp / sizeof *fp)
245         {
246           msg (SE, "Too many values in single command.");
247           return CMD_FAILURE;
248         }
249       if (!parse_fp (lexer, &fp[fp_cnt++]))
250         return CMD_FAILURE;
251
252       if (lex_token (lexer) == T_ENDCMD && fp_cnt > 1)
253         break;
254       else if (!lex_force_match (lexer, T_EQUALS))
255         return CMD_FAILURE;
256
257       if (fp_cnt == 1)
258         {
259           if (lex_match (lexer, T_EQUALS))
260             bijective = true;
261           else if (lex_match (lexer, T_GT))
262             bijective = false;
263           else
264             {
265               lex_error (lexer, NULL);
266               return CMD_FAILURE;
267             }
268         }
269       else
270         {
271           if ((bijective && !lex_force_match (lexer, T_EQUALS))
272               || (!bijective && !lex_force_match (lexer, T_GT)))
273             return CMD_FAILURE;
274         }
275     }
276
277   ok = true;
278   if (bijective)
279     {
280       size_t i, j;
281
282       for (i = 0; i < fp_cnt; i++)
283         for (j = 0; j < fp_cnt; j++)
284           if (!verify_conversion (&fp[i], &fp[j]))
285             ok = false;
286     }
287   else
288     {
289       size_t i;
290
291       for (i = 1; i < fp_cnt; i++)
292         if (!verify_conversion (&fp[i - 1], &fp[i]))
293           ok = false;
294     }
295
296   return ok ? CMD_SUCCESS : CMD_FAILURE;
297 }