aboutsummaryrefslogtreecommitdiff
path: root/gdb/dfp.c
blob: 9816d272e89bf8350737b5f9346d5d20bf5a8ea0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
/* Decimal floating point support for GDB.

   Copyright 2007, 2008 Free Software Foundation, Inc.

   This file is part of GDB.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

#include "defs.h"
#include "expression.h"
#include "gdbtypes.h"
#include "value.h"
#include "dfp.h"

/* The order of the following headers is important for making sure
   decNumber structure is large enough to hold decimal128 digits.  */

#include "dpd/decimal128.h"
#include "dpd/decimal64.h"
#include "dpd/decimal32.h"

/* In GDB, we are using an array of gdb_byte to represent decimal values.
   They are stored in host byte order.  This routine does the conversion if
   the target byte order is different.  */
static void
match_endianness (const gdb_byte *from, int len, gdb_byte *to)
{
  int i;

#if WORDS_BIGENDIAN
#define OPPOSITE_BYTE_ORDER BFD_ENDIAN_LITTLE
#else
#define OPPOSITE_BYTE_ORDER BFD_ENDIAN_BIG
#endif

  if (gdbarch_byte_order (current_gdbarch) == OPPOSITE_BYTE_ORDER)
    for (i = 0; i < len; i++)
      to[i] = from[len - i - 1];
  else
    for (i = 0; i < len; i++)
      to[i] = from[i];

  return;
}

/* Helper function to get the appropriate libdecnumber context for each size
   of decimal float.  */
static void
set_decnumber_context (decContext *ctx, int len)
{
  switch (len)
    {
      case 4:
	decContextDefault (ctx, DEC_INIT_DECIMAL32);
	break;
      case 8:
	decContextDefault (ctx, DEC_INIT_DECIMAL64);
	break;
      case 16:
	decContextDefault (ctx, DEC_INIT_DECIMAL128);
	break;
    }

  ctx->traps = 0;
}

/* Check for errors signaled in the decimal context structure.  */
static void
decimal_check_errors (decContext *ctx)
{
  /* An error here could be a division by zero, an overflow, an underflow or
     an invalid operation (from the DEC_Errors constant in decContext.h).
     Since GDB doesn't complain about division by zero, overflow or underflow
     errors for binary floating, we won't complain about them for decimal
     floating either.  */
  if (ctx->status & DEC_IEEE_854_Invalid_operation)
    {
      /* Leave only the error bits in the status flags.  */
      ctx->status &= DEC_IEEE_854_Invalid_operation;
      error (_("Cannot perform operation: %s"), decContextStatusToString (ctx));
    }
}

/* Helper function to convert from libdecnumber's appropriate representation
   for computation to each size of decimal float.  */
static void
decimal_from_number (const decNumber *from, gdb_byte *to, int len)
{
  decContext set;

  set_decnumber_context (&set, len);

  switch (len)
    {
      case 4:
	decimal32FromNumber ((decimal32 *) to, from, &set);
	break;
      case 8:
	decimal64FromNumber ((decimal64 *) to, from, &set);
	break;
      case 16:
	decimal128FromNumber ((decimal128 *) to, from, &set);
	break;
    }
}

/* Helper function to convert each size of decimal float to libdecnumber's
   appropriate representation for computation.  */
static void
decimal_to_number (const gdb_byte *from, int len, decNumber *to)
{
  switch (len)
    {
      case 4:
	decimal32ToNumber ((decimal32 *) from, to);
	break;
      case 8:
	decimal64ToNumber ((decimal64 *) from, to);
	break;
      case 16:
	decimal128ToNumber ((decimal128 *) from, to);
	break;
      default:
	error (_("Unknown decimal floating point type.\n"));
	break;
    }
}

/* Convert decimal type to its string representation.  LEN is the length
   of the decimal type, 4 bytes for decimal32, 8 bytes for decimal64 and
   16 bytes for decimal128.  */
void
decimal_to_string (const gdb_byte *decbytes, int len, char *s)
{
  gdb_byte dec[16];

  match_endianness (decbytes, len, dec);

  switch (len)
    {
      case 4:
	decimal32ToString ((decimal32 *) dec, s);
	break;
      case 8:
	decimal64ToString ((decimal64 *) dec, s);
	break;
      case 16:
	decimal128ToString ((decimal128 *) dec, s);
	break;
      default:
	error (_("Unknown decimal floating point type."));
	break;
    }
}

/* Convert the string form of a decimal value to its decimal representation.
   LEN is the length of the decimal type, 4 bytes for decimal32, 8 bytes for
   decimal64 and 16 bytes for decimal128.  */
int
decimal_from_string (gdb_byte *decbytes, int len, const char *string)
{
  decContext set;
  gdb_byte dec[16];

  set_decnumber_context (&set, len);

  switch (len)
    {
      case 4:
	decimal32FromString ((decimal32 *) dec, string, &set);
	break;
      case 8:
	decimal64FromString ((decimal64 *) dec, string, &set);
	break;
      case 16:
	decimal128FromString ((decimal128 *) dec, string, &set);
	break;
      default:
	error (_("Unknown decimal floating point type."));
	break;
    }

  match_endianness (dec, len, decbytes);

  /* Check for errors in the DFP operation.  */
  decimal_check_errors (&set);

  return 1;
}

/* Converts a value of an integral type to a decimal float of
   specified LEN bytes.  */
void
decimal_from_integral (struct value *from, gdb_byte *to, int len)
{
  LONGEST l;
  gdb_byte dec[16];
  decNumber number;
  struct type *type;

  type = check_typedef (value_type (from));

  if (TYPE_LENGTH (type) > 4)
    /* libdecnumber can convert only 32-bit integers.  */
    error (_("Conversion of large integer to a decimal floating type is not supported."));

  l = value_as_long (from);

  if (TYPE_UNSIGNED (type))
    decNumberFromUInt32 (&number, (unsigned int) l);
  else
    decNumberFromInt32 (&number, (int) l);

  decimal_from_number (&number, dec, len);
  match_endianness (dec, len, to);
}

/* Converts a value of a float type to a decimal float of
   specified LEN bytes.

   This is an ugly way to do the conversion, but libdecnumber does
   not offer a direct way to do it.  */
void
decimal_from_floating (struct value *from, gdb_byte *to, int len)
{
  char *buffer;
  int ret;

  ret = asprintf (&buffer, "%.30" DOUBLEST_PRINT_FORMAT,
		  value_as_double (from));
  if (ret < 0)
    error (_("Error in memory allocation for conversion to decimal float."));

  decimal_from_string (to, len, buffer);

  free (buffer);
}

/* Converts a decimal float of LEN bytes to a double value.  */
DOUBLEST
decimal_to_doublest (const gdb_byte *from, int len)
{
  char buffer[MAX_DECIMAL_STRING];

  /* This is an ugly way to do the conversion, but libdecnumber does
     not offer a direct way to do it.  */
  decimal_to_string (from, len, buffer);
  return strtod (buffer, NULL);
}

/* Check if operands have the same size and convert them to the
   biggest of the two if necessary.  */
static int
promote_decimal (gdb_byte *x, int len_x, gdb_byte *y, int len_y)
{
  int len_result;
  decNumber number;

  if (len_x < len_y)
    {
      decimal_to_number (x, len_x, &number);
      decimal_from_number (&number, x, len_y);
      len_result = len_y;
    }
  else if (len_x > len_y)
    {
      decimal_to_number (y, len_y, &number);
      decimal_from_number (&number, y, len_x);
      len_result = len_x;
    }
  else
    len_result = len_x;

  return len_result;
}

/* Perform operation OP with operands X and Y and store value in RESULT.
   If LEN_X and LEN_Y are not equal, RESULT will have the size of the biggest
   of the two, and LEN_RESULT will be set accordingly.  */
void
decimal_binop (enum exp_opcode op, const gdb_byte *x, int len_x,
	       const gdb_byte *y, int len_y, gdb_byte *result, int *len_result)
{
  decContext set;
  decNumber number1, number2, number3;
  gdb_byte dec1[16], dec2[16], dec3[16];

  match_endianness (x, len_x, dec1);
  match_endianness (y, len_y, dec2);

  *len_result = promote_decimal (dec1, len_x, dec2, len_y);

  /* Both operands are of size *len_result from now on.  */

  decimal_to_number (dec1, *len_result, &number1);
  decimal_to_number (dec2, *len_result, &number2);

  set_decnumber_context (&set, *len_result);

  switch (op)
    {
      case BINOP_ADD:
	decNumberAdd (&number3, &number1, &number2, &set);
	break;
      case BINOP_SUB:
	decNumberSubtract (&number3, &number1, &number2, &set);
	break;
      case BINOP_MUL:
	decNumberMultiply (&number3, &number1, &number2, &set);
	break;
      case BINOP_DIV:
	decNumberDivide (&number3, &number1, &number2, &set);
	break;
      case BINOP_EXP:
	decNumberPower (&number3, &number1, &number2, &set);
	break;
      default:
	internal_error (__FILE__, __LINE__,
			_("Unknown decimal floating point operation."));
	break;
    }

  /* Check for errors in the DFP operation.  */
  decimal_check_errors (&set);

  decimal_from_number (&number3, dec3, *len_result);

  match_endianness (dec3, *len_result, result);
}

/* Returns true if X (which is LEN bytes wide) is the number zero.  */
int
decimal_is_zero (const gdb_byte *x, int len)
{
  decNumber number;
  gdb_byte dec[16];

  match_endianness (x, len, dec);
  decimal_to_number (dec, len, &number);

  return decNumberIsZero (&number);
}

/* Compares two numbers numerically.  If X is less than Y then the return value
   will be -1.  If they are equal, then the return value will be 0.  If X is
   greater than the Y then the return value will be 1.  */
int
decimal_compare (const gdb_byte *x, int len_x, const gdb_byte *y, int len_y)
{
  decNumber number1, number2, result;
  decContext set;
  gdb_byte dec1[16], dec2[16];
  int len_result;

  match_endianness (x, len_x, dec1);
  match_endianness (y, len_y, dec2);

  len_result = promote_decimal (dec1, len_x, dec2, len_y);

  decimal_to_number (dec1, len_result, &number1);
  decimal_to_number (dec2, len_result, &number2);

  set_decnumber_context (&set, len_result);

  decNumberCompare (&result, &number1, &number2, &set);

  /* Check for errors in the DFP operation.  */
  decimal_check_errors (&set);

  if (decNumberIsNaN (&result))
    error (_("Comparison with an invalid number (NaN)."));
  else if (decNumberIsZero (&result))
    return 0;
  else if (decNumberIsNegative (&result))
    return -1;
  else
    return 1;
}

/* Convert a decimal value from a decimal type with LEN_FROM bytes to a
   decimal type with LEN_TO bytes.  */
void
decimal_convert (const gdb_byte *from, int len_from, gdb_byte *to,
		 int len_to)
{
  decNumber number;
  gdb_byte dec[16];

  match_endianness (from, len_from, dec);

  decimal_to_number (dec, len_from, &number);
  decimal_from_number (&number, dec, len_to);

  match_endianness (dec, len_to, to);
}