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