Index: binutils-gdb/gdb/common/format.c
===================================================================
--- binutils-gdb.orig/gdb/common/format.c
+++ binutils-gdb/gdb/common/format.c
@@ -274,8 +274,12 @@ parse_format_string (const char **arg)
 	  case 'g':
 	  case 'E':
 	  case 'G':
-	    if (seen_big_h || seen_big_d || seen_double_big_d)
-	      this_argclass = decfloat_arg;
+	    if (seen_double_big_d)
+	      this_argclass = dec128float_arg;
+	    else if (seen_big_d)
+	      this_argclass = dec64float_arg;
+	    else if (seen_big_h)
+	      this_argclass = dec32float_arg;
 	    else if (seen_big_l)
 	      this_argclass = long_double_arg;
 	    else
Index: binutils-gdb/gdb/common/format.h
===================================================================
--- binutils-gdb.orig/gdb/common/format.h
+++ binutils-gdb/gdb/common/format.h
@@ -35,7 +35,8 @@ enum argclass
     literal_piece,
     int_arg, long_arg, long_long_arg, ptr_arg,
     string_arg, wide_string_arg, wide_char_arg,
-    double_arg, long_double_arg, decfloat_arg
+    double_arg, long_double_arg,
+    dec32float_arg, dec64float_arg, dec128float_arg
   };
 
 /* A format piece is a section of the format string that may include a
Index: binutils-gdb/gdb/printcmd.c
===================================================================
--- binutils-gdb.orig/gdb/printcmd.c
+++ binutils-gdb/gdb/printcmd.c
@@ -2288,86 +2288,78 @@ printf_wide_c_string (struct ui_file *st
 }
 
 /* Subroutine of ui_printf to simplify it.
-   Print VALUE, a decimal floating point value, to STREAM using FORMAT.  */
+   Print VALUE, a floating point value, to STREAM using FORMAT.  */
 
 static void
-printf_decfloat (struct ui_file *stream, const char *format,
-		 struct value *value)
+printf_floating (struct ui_file *stream, const char *format,
+		 struct value *value, enum argclass argclass)
 {
-  const gdb_byte *param_ptr = value_contents (value);
-
-#if defined (PRINTF_HAS_DECFLOAT)
-  /* If we have native support for Decimal floating
-     printing, handle it here.  */
-  fprintf_filtered (stream, format, param_ptr);
-#else
-  /* As a workaround until vasprintf has native support for DFP
-     we convert the DFP values to string and print them using
-     the %s format specifier.  */
-  const char *p;
-
   /* Parameter data.  */
   struct type *param_type = value_type (value);
   struct gdbarch *gdbarch = get_type_arch (param_type);
   enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
 
-  /* DFP output data.  */
-  struct value *dfp_value = NULL;
-  gdb_byte *dfp_ptr;
-  int dfp_len = 16;
-  gdb_byte dec[16];
-  struct type *dfp_type = NULL;
-
-  /* Points to the end of the string so that we can go back
-     and check for DFP length modifiers.  */
-  p = format + strlen (format);
-
-  /* Look for the float/double format specifier.  */
-  while (*p != 'f' && *p != 'e' && *p != 'E'
-	 && *p != 'g' && *p != 'G')
-    p--;
-
-  /* Search for the '%' char and extract the size and type of
-     the output decimal value based on its modifiers
-     (%Hf, %Df, %DDf).  */
-  while (*--p != '%')
+  /* Determine target type corresponding to the format string.  */
+  struct type *fmt_type;
+  switch (argclass)
     {
-      if (*p == 'H')
-	{
-	  dfp_len = 4;
-	  dfp_type = builtin_type (gdbarch)->builtin_decfloat;
-	}
-      else if (*p == 'D' && *(p - 1) == 'D')
-	{
-	  dfp_len = 16;
-	  dfp_type = builtin_type (gdbarch)->builtin_declong;
-	  p--;
-	}
-      else
-	{
-	  dfp_len = 8;
-	  dfp_type = builtin_type (gdbarch)->builtin_decdouble;
-	}
+      case double_arg:
+	fmt_type = builtin_type (gdbarch)->builtin_double;
+	break;
+      case long_double_arg:
+	fmt_type = builtin_type (gdbarch)->builtin_long_double;
+	break;
+      case dec32float_arg:
+	fmt_type = builtin_type (gdbarch)->builtin_decfloat;
+	break;
+      case dec64float_arg:
+	fmt_type = builtin_type (gdbarch)->builtin_decdouble;
+	break;
+      case dec128float_arg:
+	fmt_type = builtin_type (gdbarch)->builtin_declong;
+	break;
+      default:
+	gdb_assert_not_reached ("unexpected argument class");
     }
 
-  /* Conversion between different DFP types.  */
-  if (TYPE_CODE (param_type) == TYPE_CODE_DECFLOAT)
-    decimal_convert (param_ptr, TYPE_LENGTH (param_type),
-		     byte_order, dec, dfp_len, byte_order);
-  else
-    /* If this is a non-trivial conversion, just output 0.
-       A correct converted value can be displayed by explicitly
-       casting to a DFP type.  */
-    decimal_from_string (dec, dfp_len, byte_order, "0");
+  /* To match the traditional GDB behavior, the conversion is
+     done differently depending on the type of the parameter:
+
+     - if the parameter has floating-point type, it's value
+       is converted to the target type;
+
+     - otherwise, if the parameter has a type that is of the
+       same size as a built-in floating-point type, the value
+       bytes are interpreted as if they were of that type, and
+       then converted to the target type (this is not done for
+       decimal floating-point argument classes);
+
+     - otherwise, if the source value has an integer value,
+       it's value is converted to the target type;
 
-  dfp_value = value_from_decfloat (dfp_type, dec);
+     - otherwise, an error is raised.
+
+     In either case, the result of the conversion is a byte buffer
+     formatted in the target format for the target type.  */
+
+  if (TYPE_CODE (fmt_type) == TYPE_CODE_FLT)
+    {
+      param_type = float_type_from_length (param_type);
+      if (param_type != value_type (value))
+	value = value_from_contents (param_type, value_contents (value));
+    }
 
-  dfp_ptr = (gdb_byte *) value_contents (dfp_value);
+  value = value_cast (fmt_type, value);
 
   /* Convert the value to a string and print it.  */
-  std::string str = decimal_to_string (dfp_ptr, dfp_len, byte_order);
+  std::string str;
+  if (TYPE_CODE (fmt_type) == TYPE_CODE_FLT)
+    str = floatformat_to_string (floatformat_from_type (fmt_type),
+				 value_contents (value), format);
+  else
+    str = decimal_to_string (value_contents (value),
+			     TYPE_LENGTH (fmt_type), byte_order, format);
   fputs_filtered (str.c_str (), stream);
-#endif
 }
 
 /* Subroutine of ui_printf to simplify it.
@@ -2550,43 +2542,6 @@ ui_printf (const char *arg, struct ui_fi
                                 obstack_base (&output));
 	    }
 	    break;
-	  case double_arg:
-	    {
-	      struct type *type = value_type (val_args[i]);
-	      DOUBLEST val;
-	      int inv;
-
-	      /* If format string wants a float, unchecked-convert the value
-		 to floating point of the same size.  */
-	      type = float_type_from_length (type);
-	      val = unpack_double (type, value_contents (val_args[i]), &inv);
-	      if (inv)
-		error (_("Invalid floating value found in program."));
-
-              fprintf_filtered (stream, current_substring, (double) val);
-	      break;
-	    }
-	  case long_double_arg:
-#ifdef HAVE_LONG_DOUBLE
-	    {
-	      struct type *type = value_type (val_args[i]);
-	      DOUBLEST val;
-	      int inv;
-
-	      /* If format string wants a float, unchecked-convert the value
-		 to floating point of the same size.  */
-	      type = float_type_from_length (type);
-	      val = unpack_double (type, value_contents (val_args[i]), &inv);
-	      if (inv)
-		error (_("Invalid floating value found in program."));
-
-	      fprintf_filtered (stream, current_substring,
-                                (long double) val);
-	      break;
-	    }
-#else
-	    error (_("long double not supported in printf"));
-#endif
 	  case long_long_arg:
 #ifdef PRINTF_HAS_LONG_LONG
 	    {
@@ -2612,9 +2567,14 @@ ui_printf (const char *arg, struct ui_fi
               fprintf_filtered (stream, current_substring, val);
 	      break;
 	    }
-	  /* Handles decimal floating values.  */
-	  case decfloat_arg:
-	    printf_decfloat (stream, current_substring, val_args[i]);
+	  /* Handles floating-point values.  */
+	  case double_arg:
+	  case long_double_arg:
+	  case dec32float_arg:
+	  case dec64float_arg:
+	  case dec128float_arg:
+	    printf_floating (stream, current_substring, val_args[i],
+			     fpieces[fr].argclass);
 	    break;
 	  case ptr_arg:
 	    printf_pointer (stream, current_substring, val_args[i]);
Index: binutils-gdb/gdb/dfp.c
===================================================================
--- binutils-gdb.orig/gdb/dfp.c
+++ binutils-gdb/gdb/dfp.c
@@ -146,12 +146,23 @@ decimal_to_number (const gdb_byte *from,
    16 bytes for decimal128.  */
 std::string
 decimal_to_string (const gdb_byte *decbytes, int len,
-		   enum bfd_endian byte_order)
+		   enum bfd_endian byte_order, const char *format)
 {
   gdb_byte dec[16];
 
   match_endianness (decbytes, len, byte_order, dec);
 
+  if (format != nullptr)
+    {
+      /* We don't handle format strings (yet).  If the host printf supports
+	 decimal floating point types, just use this.  Otherwise, fall back
+	 to printing the number while ignoring the format string.  */
+#if defined (PRINTF_HAS_DECFLOAT)
+      /* FIXME: This makes unwarranted assumptions about the host ABI!  */
+      return string_printf (format, dec);
+#endif
+    }
+
   std::string result;
   result.resize (MAX_DECIMAL_STRING);
 
Index: binutils-gdb/gdb/dfp.h
===================================================================
--- binutils-gdb.orig/gdb/dfp.h
+++ binutils-gdb/gdb/dfp.h
@@ -28,7 +28,8 @@
 #include "doublest.h"    /* For DOUBLEST.  */
 #include "expression.h"  /* For enum exp_opcode.  */
 
-extern std::string decimal_to_string (const gdb_byte *, int, enum bfd_endian);
+extern std::string decimal_to_string (const gdb_byte *, int, enum bfd_endian,
+				      const char *format = nullptr);
 extern bool decimal_from_string (gdb_byte *, int, enum bfd_endian,
 				 std::string string);
 extern void decimal_from_longest (LONGEST from, gdb_byte *to,
Index: binutils-gdb/gdb/doublest.c
===================================================================
--- binutils-gdb.orig/gdb/doublest.c
+++ binutils-gdb/gdb/doublest.c
@@ -789,10 +789,10 @@ floatformat_from_doublest (const struct
 }
 
 /* Convert the byte-stream ADDR, interpreted as floating-point format FMT,
-   to a string.  */
+   to a string, optionally using the print format FORMAT.  */
 std::string
 floatformat_to_string (const struct floatformat *fmt,
-		       const gdb_byte *in)
+		       const gdb_byte *in, const char *format)
 {
   /* Detect invalid representations.  */
   if (!floatformat_is_valid (fmt, in))
@@ -812,22 +812,50 @@ floatformat_to_string (const struct floa
       return string_printf ("%sinf", sign);
     }
 
-  /* Print the number using a format string where the precision is set
-     to the DECIMAL_DIG value for the given floating-point format.
-     This value is computed as
+  /* Determine the format string to use on the host side.  */
+  std::string host_format;
+  char conversion;
+
+  if (format == nullptr)
+    {
+      /* If no format was specified, print the number using a format string
+	 where the precision is set to the DECIMAL_DIG value for the given
+	 floating-point format.  This value is computed as
 
 		ceil(1 + p * log10(b)),
 
-     where p is the precision of the floating-point format in bits, and
-     b is the base (which is always 2 for the formats we support).  */
-  const double log10_2 = .30102999566398119521;
-  double d_decimal_dig = 1 + floatformat_precision (fmt) * log10_2;
-  int decimal_dig = d_decimal_dig;
-  if (decimal_dig < d_decimal_dig)
-    decimal_dig++;
+	 where p is the precision of the floating-point format in bits, and
+	 b is the base (which is always 2 for the formats we support).  */
+      const double log10_2 = .30102999566398119521;
+      double d_decimal_dig = 1 + floatformat_precision (fmt) * log10_2;
+      int decimal_dig = d_decimal_dig;
+      if (decimal_dig < d_decimal_dig)
+	decimal_dig++;
+
+      host_format = string_printf ("%%.%d", decimal_dig);
+      conversion = 'g';
+    }
+  else
+    {
+      /* Use the specified format, stripping out the conversion character
+         and length modifier, if present.  */
+      size_t len = strlen (format);
+      gdb_assert (len > 1);
+      conversion = format[--len];
+      gdb_assert (conversion == 'e' || conversion == 'f' || conversion == 'g'
+		  || conversion == 'E' || conversion == 'G');
+      if (format[len - 1] == 'L')
+	len--;
+
+      host_format = std::string (format, len);
+    }
 
-  std::string host_format
-    = string_printf ("%%.%d" DOUBLEST_PRINT_FORMAT, decimal_dig);
+  /* Add the length modifier and conversion character appropriate for
+     handling the host DOUBLEST type.  */
+#ifdef HAVE_LONG_DOUBLE
+  host_format += 'L';
+#endif
+  host_format += conversion;
 
   DOUBLEST doub;
   floatformat_to_doublest (fmt, in, &doub);
Index: binutils-gdb/gdb/doublest.h
===================================================================
--- binutils-gdb.orig/gdb/doublest.h
+++ binutils-gdb/gdb/doublest.h
@@ -72,7 +72,8 @@ extern const char *floatformat_mantissa
 					 const bfd_byte *);
 
 extern std::string floatformat_to_string (const struct floatformat *fmt,
-					  const gdb_byte *in);
+					  const gdb_byte *in,
+					  const char *format = nullptr);
 
 /* Return the floatformat's total size in host bytes.  */
 
