Work on support for variable sets.
[pspp] / src / data / format.c
index a4c52b788ebd811541b9e3ac7d0e25beab016d81..87a40e2eed32a75785a9ef7302bd86bfcbe95f59 100644 (file)
@@ -101,26 +101,38 @@ fmt_settings_get_style (const struct fmt_settings *settings,
 
 #define OPPOSITE(C) ((C) == ',' ? '.' : ',')
 #define AFFIX(S) { .s = (char *) (S), .width = sizeof (S) - 1 }
-#define NS(PREFIX, SUFFIX, DECIMAL, GROUPING) { \
+#define NS(PREFIX, SUFFIX, DECIMAL, GROUPING, INCLUDE_LEADING_ZERO) {        \
     .neg_prefix = AFFIX ("-"),                  \
     .prefix = AFFIX (PREFIX),                   \
     .suffix = AFFIX (SUFFIX),                   \
     .neg_suffix = AFFIX (""),                   \
     .decimal = DECIMAL,                         \
     .grouping = GROUPING,                       \
+    .include_leading_zero = INCLUDE_LEADING_ZERO \
   }
-#define ANS(DECIMAL, GROUPING) {                        \
-    [FMT_F]      = NS( "",  "", DECIMAL, 0),            \
-    [FMT_E]      = NS( "",  "", DECIMAL, 0),            \
-    [FMT_COMMA]  = NS( "",  "", DECIMAL, GROUPING),     \
-    [FMT_DOT]    = NS( "",  "", GROUPING, DECIMAL),     \
-    [FMT_DOLLAR] = NS("$",  "", DECIMAL, GROUPING),     \
-    [FMT_PCT]    = NS( "", "%", DECIMAL, 0),            \
+#define ANS(DECIMAL, GROUPING, INCLUDE_LEADING_ZERO) {                  \
+    [FMT_F]      = NS( "",  "", DECIMAL, 0, INCLUDE_LEADING_ZERO),      \
+    [FMT_E]      = NS( "",  "", DECIMAL, 0, INCLUDE_LEADING_ZERO),      \
+    [FMT_COMMA]  = NS( "",  "", DECIMAL, GROUPING, INCLUDE_LEADING_ZERO), \
+    [FMT_DOT]    = NS( "",  "", GROUPING, DECIMAL, INCLUDE_LEADING_ZERO), \
+    [FMT_DOLLAR] = NS("$",  "", DECIMAL, GROUPING, false),              \
+    [FMT_PCT]    = NS( "", "%", DECIMAL, 0, false),                     \
   }
+#define ANS2(DECIMAL, GROUPING) {               \
+    ANS(DECIMAL, GROUPING, false),              \
+    ANS(DECIMAL, GROUPING, true),               \
+  }
+
+  /* First index: 0 for ',' decimal point, 1 for '.' decimal point.
+     Second index: 0 for no leading zero, 1 for leading zero.
+     Third index: TYPE.
+  */
+  static const struct fmt_number_style styles[2][2][6] = {
+    ANS2 (',', '.'),
+    ANS2 ('.', ','),
+  };
 
-  static const struct fmt_number_style period_styles[6] = ANS ('.', ',');
-  static const struct fmt_number_style comma_styles[6] = ANS (',', '.');
-  static const struct fmt_number_style default_style = NS ("", "", '.', 0);
+  static const struct fmt_number_style default_style = NS ("", "", '.', 0, false);
 
   switch (type)
     {
@@ -130,9 +142,11 @@ fmt_settings_get_style (const struct fmt_settings *settings,
     case FMT_DOLLAR:
     case FMT_PCT:
     case FMT_E:
-      return (settings->decimal == '.'
-              ? &period_styles[type]
-              : &comma_styles[type]);
+      {
+        int decimal_idx = settings->decimal == '.';
+        int leadzero_idx = settings->include_leading_zero;
+        return &styles[decimal_idx][leadzero_idx][type];
+      }
 
     case FMT_CCA:
     case FMT_CCB:
@@ -184,11 +198,8 @@ fmt_settings_set_cc (struct fmt_settings *settings, enum fmt_type type,
 struct fmt_spec
 fmt_for_input (enum fmt_type type, int w, int d)
 {
-  struct fmt_spec f;
-  f.type = type;
-  f.w = w;
-  f.d = d;
-  assert (fmt_check_input (&f));
+  struct fmt_spec f = { .type = type, .w = w, .d = d };
+  assert (fmt_check_input (f));
   return f;
 }
 
@@ -197,33 +208,30 @@ fmt_for_input (enum fmt_type type, int w, int d)
 struct fmt_spec
 fmt_for_output (enum fmt_type type, int w, int d)
 {
-  struct fmt_spec f;
-  f.type = type;
-  f.w = w;
-  f.d = d;
-  assert (fmt_check_output (&f));
+  struct fmt_spec f = { .type = type, .w = w, .d = d };
+  assert (fmt_check_output (f));
   return f;
 }
 
 /* Returns the output format specifier corresponding to input
    format specifier INPUT. */
 struct fmt_spec
-fmt_for_output_from_input (const struct fmt_spec *input,
+fmt_for_output_from_input (struct fmt_spec input,
                            const struct fmt_settings *settings)
 {
   struct fmt_spec output;
 
   assert (fmt_check_input (input));
 
-  output.type = fmt_input_to_output (input->type);
-  output.w = input->w;
+  output.type = fmt_input_to_output (input.type);
+  output.w = input.w;
   if (output.w > fmt_max_output_width (output.type))
     output.w = fmt_max_output_width (output.type);
   else if (output.w < fmt_min_output_width (output.type))
     output.w = fmt_min_output_width (output.type);
-  output.d = input->d;
+  output.d = input.d;
 
-  switch (input->type)
+  switch (input.type)
     {
     case FMT_Z:
       output.w++;
@@ -238,11 +246,11 @@ fmt_for_output_from_input (const struct fmt_spec *input,
     case FMT_PCT:
       {
         const struct fmt_number_style *style =
-         fmt_settings_get_style (settings, input->type);
+         fmt_settings_get_style (settings, input.type);
 
         output.w += fmt_affix_width (style);
-        if (style->grouping != 0 && input->w - input->d >= 3)
-          output.w += (input->w - input->d - 1) / 3;
+        if (style->grouping != 0 && input.w - input.d >= 3)
+          output.w += (input.w - input.d - 1) / 3;
         if (output.d > 0)
           output.w++;
       }
@@ -254,12 +262,12 @@ fmt_for_output_from_input (const struct fmt_spec *input,
       break;
 
     case FMT_E:
-      output.d = MAX (input->d, 3);
-      output.w = MAX (input->w, output.d + 7);
+      output.d = MAX (input.d, 3);
+      output.w = MAX (input.w, output.d + 7);
       break;
 
     case FMT_PIBHEX:
-      output.w = max_digits_for_bytes (input->w / 2) + 1;
+      output.w = max_digits_for_bytes (input.w / 2) + 1;
       break;
 
     case FMT_RB:
@@ -270,12 +278,12 @@ fmt_for_output_from_input (const struct fmt_spec *input,
 
     case FMT_P:
     case FMT_PK:
-      output.w = 2 * input->w + (input->d > 0);
+      output.w = 2 * input.w + (input.d > 0);
       break;
 
     case FMT_IB:
     case FMT_PIB:
-      output.w = max_digits_for_bytes (input->w) + 1;
+      output.w = max_digits_for_bytes (input.w) + 1;
       if (output.d > 0)
         output.w++;
       break;
@@ -291,7 +299,7 @@ fmt_for_output_from_input (const struct fmt_spec *input,
       break;
 
     case FMT_AHEX:
-      output.w = input->w / 2;
+      output.w = input.w / 2;
       break;
 
     case FMT_DATE:
@@ -310,13 +318,13 @@ fmt_for_output_from_input (const struct fmt_spec *input,
       break;
 
     case FMT_MTIME:
-      if (input->d)
-        output.w = MAX (input->w, input->d + 6);
+      if (input.d)
+        output.w = MAX (input.w, input.d + 6);
       break;
 
     case FMT_YMDHMS:
-      if (input->w)
-        output.w = MAX (input->w, input->d + 20);
+      if (input.w)
+        output.w = MAX (input.w, input.d + 20);
       break;
 
     default:
@@ -326,7 +334,7 @@ fmt_for_output_from_input (const struct fmt_spec *input,
   if (output.w > fmt_max_output_width (output.type))
     output.w = fmt_max_output_width (output.type);
 
-  assert (fmt_check_output (&output));
+  assert (fmt_check_output (output));
   return output;
 }
 
@@ -340,138 +348,242 @@ fmt_default_for_width (int width)
           : fmt_for_output (FMT_A, width, 0));
 }
 
-/* Checks whether SPEC is valid for USE and returns nonzero if so.
-   Otherwise, emits an error message and returns zero. */
-bool
-fmt_check (const struct fmt_spec *spec, enum fmt_use use)
+/* Checks whether SPEC is valid for USE and returns NULL if so.  Otherwise,
+   returns a malloc()'d string that describes the error.  The caller must
+   eventually free() the string. */
+char *
+fmt_check__ (struct fmt_spec spec, enum fmt_use use)
 {
-  const char *io_fmt;
   char str[FMT_STRING_LEN_MAX + 1];
   int min_w, max_w, max_d;
 
-  assert (is_fmt_type (spec->type));
+  assert (is_fmt_type (spec.type));
   fmt_to_string (spec, str);
 
-  io_fmt = use == FMT_FOR_INPUT ? _("Input format") : _("Output format");
-  if (use == FMT_FOR_INPUT && !fmt_usable_for_input (spec->type))
-    {
-      msg (SE, _("Format %s may not be used for input."), str);
-      return false;
-    }
+  if (use == FMT_FOR_INPUT && !fmt_usable_for_input (spec.type))
+    return xasprintf (_("Format %s may not be used for input."), str);
 
-  if (spec->w % fmt_step_width (spec->type))
+  if (spec.w % fmt_step_width (spec.type))
     {
-      assert (fmt_step_width (spec->type) == 2);
-      msg (SE, _("%s specifies width %d, but %s requires an even width."),
-           str, spec->w, fmt_name (spec->type));
-      return false;
+      assert (fmt_step_width (spec.type) == 2);
+      return (use == FMT_FOR_INPUT
+              ? xasprintf (_("Input format %s specifies width %d, "
+                             "but %s requires an even width."),
+                           str, spec.w, fmt_name (spec.type))
+              : xasprintf (_("Output format %s specifies width %d, "
+                             "but %s requires an even width."),
+                           str, spec.w, fmt_name (spec.type)));
     }
 
-  min_w = fmt_min_width (spec->type, use);
-  max_w = fmt_max_width (spec->type, use);
-  if (spec->w < min_w || spec->w > max_w)
+  min_w = fmt_min_width (spec.type, use);
+  max_w = fmt_max_width (spec.type, use);
+  if (spec.w < min_w || spec.w > max_w)
+    return (use == FMT_FOR_INPUT
+            ? xasprintf (_("Input format %s specifies width %d, but "
+                           "%s requires a width between %d and %d."),
+                         str, spec.w, fmt_name (spec.type), min_w, max_w)
+            : xasprintf (_("Output format %s specifies width %d, but "
+                           "%s requires a width between %d and %d."),
+                         str, spec.w, fmt_name (spec.type), min_w, max_w));
+
+  max_d = fmt_max_decimals (spec.type, spec.w, use);
+  if (!fmt_takes_decimals (spec.type) && spec.d != 0)
+    return (use == FMT_FOR_INPUT
+            ? xasprintf (ngettext (
+                           "Input format %s specifies %d decimal "
+                           "place, but %s does not allow any decimals.",
+                           "Input format %s specifies %d decimal "
+                           "places, but %s does not allow any "
+                           "decimals.",
+                           spec.d),
+                         str, spec.d, fmt_name (spec.type))
+            : xasprintf (ngettext (
+                           "Output format %s specifies %d decimal "
+                           "place, but %s does not allow any decimals.",
+                           "Output format %s specifies %d decimal places, but "
+                           "%s does not allow any decimals.",
+                           spec.d),
+                         str, spec.d, fmt_name (spec.type)));
+  else if (spec.d > max_d)
     {
-      msg (SE, _("%s %s specifies width %d, but "
-                 "%s requires a width between %d and %d."),
-           io_fmt, str, spec->w, fmt_name (spec->type), min_w, max_w);
-      return false;
+      if (max_d > 0)
+        return (use == FMT_FOR_INPUT
+                ? xasprintf (ngettext (
+                               "Input format %s specifies %d decimal place, "
+                               "but width %d allows at most %d decimals.",
+                               "Input format %s specifies %d decimal places, "
+                               "but width %d allows at most %d decimals.",
+                               spec.d),
+                             str, spec.d, spec.w, max_d)
+                : xasprintf (ngettext (
+                               "Output format %s specifies %d decimal place, "
+                               "but width %d allows at most %d decimals.",
+                               "Output format %s specifies %d decimal places, "
+                               "but width %d allows at most %d decimals.",
+                               spec.d),
+                             str, spec.d, spec.w, max_d));
+      else
+        return (use == FMT_FOR_INPUT
+                ? xasprintf (ngettext (
+                               "Input format %s specifies %d decimal place, "
+                               "but width %d does not allow for any decimals.",
+                               "Input format %s specifies %d decimal places, "
+                               "but width %d does not allow for any decimals.",
+                               spec.d),
+                             str, spec.d, spec.w)
+                : xasprintf (ngettext (
+                               "Output format %s specifies %d decimal place, "
+                               "but width %d does not allow for any decimals.",
+                               "Output format %s specifies %d decimal places, "
+                               "but width %d does not allow for any decimals.",
+                               spec.d),
+                             str, spec.d, spec.w));
     }
 
-  max_d = fmt_max_decimals (spec->type, spec->w, use);
-  if (!fmt_takes_decimals (spec->type) && spec->d != 0)
-    {
-      msg (SE, ngettext ("%s %s specifies %d decimal place, but "
-                         "%s does not allow any decimals.",
-                         "%s %s specifies %d decimal places, but "
-                         "%s does not allow any decimals.",
-                         spec->d),
-           io_fmt, str, spec->d, fmt_name (spec->type));
-      return false;
-    }
-  else if (spec->d > max_d)
+  return NULL;
+}
+
+char *
+fmt_check_input__ (struct fmt_spec spec)
+{
+  return fmt_check__ (spec, FMT_FOR_INPUT);
+}
+
+char *
+fmt_check_output__ (struct fmt_spec spec)
+{
+  return fmt_check__ (spec, FMT_FOR_OUTPUT);
+}
+
+static bool
+error_to_bool (char *error)
+{
+  if (error)
     {
-      if (max_d > 0)
-        msg (SE, ngettext ("%s %s specifies %d decimal place, but "
-                           "the given width allows at most %d decimals.",
-                           "%s %s specifies %d decimal places, but "
-                           "the given width allows at most %d decimals.",
-                           spec->d),
-             io_fmt, str, spec->d, max_d);
-      else
-        msg (SE, ngettext ("%s %s specifies %d decimal place, but "
-                           "the given width does not allow for any decimals.",
-                           "%s %s specifies %d decimal places, but "
-                           "the given width does not allow for any decimals.",
-                           spec->d),
-             io_fmt, str, spec->d);
+      free (error);
       return false;
     }
+  else
+    return true;
+}
 
-  return true;
+/* Returns true if SPEC is valid for USE, false otherwise. */
+bool
+fmt_check (struct fmt_spec spec, enum fmt_use use)
+{
+  return error_to_bool (fmt_check__ (spec, use));
 }
 
-/* Checks whether SPEC is valid as an input format and returns
-   nonzero if so.  Otherwise, emits an error message and returns
-   zero. */
+/* Returns true if SPEC is valid as an input format, otherwise false. */
 bool
-fmt_check_input (const struct fmt_spec *spec)
+fmt_check_input (struct fmt_spec spec)
 {
   return fmt_check (spec, FMT_FOR_INPUT);
 }
 
-/* Checks whether SPEC is valid as an output format and returns
-   true if so.  Otherwise, emits an error message and returns false. */
+/* Returnst true SPEC is valid as an output format, false otherwise. */
 bool
-fmt_check_output (const struct fmt_spec *spec)
+fmt_check_output (struct fmt_spec spec)
 {
   return fmt_check (spec, FMT_FOR_OUTPUT);
 }
 
-/* Checks that FORMAT is appropriate for a variable of the given
-   VAR_TYPE and returns true if so.  Otherwise returns false and
-   emits an error message. */
-bool
-fmt_check_type_compat (const struct fmt_spec *format, enum val_type var_type)
+/* Checks that FORMAT is appropriate for a variable of the given VAR_TYPE and
+   returns NULL if so.  Otherwise returns a malloc()'d error message that the
+   caller must eventually free().  VARNAME is optional and only used in the
+   error message.*/
+char *
+fmt_check_type_compat__ (struct fmt_spec format, const char *varname,
+                         enum val_type var_type)
 {
   assert (val_type_is_valid (var_type));
-  if ((var_type == VAL_STRING) != (fmt_is_string (format->type) != 0))
+  if ((var_type == VAL_STRING) != (fmt_is_string (format.type) != 0))
     {
       char str[FMT_STRING_LEN_MAX + 1];
-      msg (SE, _("%s variables are not compatible with %s format %s."),
-           var_type == VAL_STRING ? _("String") : _("Numeric"),
-           var_type == VAL_STRING ? _("numeric") : _("string"),
-           fmt_to_string (format, str));
-      return false;
+      fmt_to_string (format, str);
+      if (var_type == VAL_STRING)
+        {
+          if (varname)
+            return xasprintf (_("String variable %s is not compatible with "
+                                "numeric format %s."), varname, str);
+          else
+            return xasprintf (_("String variables are not compatible with "
+                                "numeric format %s."), str);
+        }
+      else
+        {
+          if (varname)
+            return xasprintf (_("Numeric variable %s is not compatible with "
+                                "string format %s."), varname, str);
+          else
+            return xasprintf (_("Numeric variables are not compatible with "
+                                "string format %s."), str);
+        }
     }
-  return true;
+  return NULL;
 }
 
-/* Checks that FORMAT is appropriate for a variable of the given
-   WIDTH and returns true if so.  Otherwise returns false and
-   emits an error message. */
+/* Returns FORMAT is appropriate for a variable of the given VAR_TYPE and
+   returns true if so, otherwise false. */
 bool
-fmt_check_width_compat (const struct fmt_spec *format, int width)
+fmt_check_type_compat (struct fmt_spec format, enum val_type var_type)
 {
-  if (!fmt_check_type_compat (format, val_type_from_width (width)))
-    return false;
+  return error_to_bool (fmt_check_type_compat__ (format, NULL, var_type));
+}
+
+/* Checks that FORMAT is appropriate for a variable of the given WIDTH and
+   returns NULL if so.  Otherwise returns a malloc()'d error message that the
+   caller must eventually free().  VARNAME is optional and only used in the
+   error message. */
+char *
+fmt_check_width_compat__ (struct fmt_spec format, const char *varname,
+                          int width)
+{
+  char *error = fmt_check_type_compat__ (format, varname,
+                                         val_type_from_width (width));
+  if (error)
+    return error;
+
   if (fmt_var_width (format) != width)
     {
-      char str[FMT_STRING_LEN_MAX + 1];
-      msg (SE, _("String variable with width %d is not compatible with "
-                 "format %s."),
-           width, fmt_to_string (format, str));
-      return false;
+      char format_str[FMT_STRING_LEN_MAX + 1];
+      fmt_to_string (format, format_str);
+
+      char better_str[FMT_STRING_LEN_MAX + 1];
+      if (format.type == FMT_A)
+        snprintf (better_str, sizeof better_str, "A%d", width);
+      else
+        snprintf (better_str, sizeof better_str, "AHEX%d", width * 2);
+
+      if (varname)
+        return xasprintf (_("String variable %s with width %d is not "
+                            "compatible with format %s.  "
+                            "Use format %s instead."),
+                          varname, width, format_str, better_str);
+      else
+        return xasprintf (_("String variable with width %d is not compatible "
+                            "with format %s.  Use format %s instead."),
+                          width, format_str, better_str);
     }
-  return true;
+
+  return NULL;
+}
+
+/* Checks that FORMAT is appropriate for a variable of the given WIDTH and
+   returns true if so, otherwise false. */
+bool
+fmt_check_width_compat (struct fmt_spec format, int width)
+{
+  return error_to_bool (fmt_check_width_compat__ (format, NULL, width));
 }
 
 /* Returns the width corresponding to FORMAT.  The return value
    is the width of the `union value's required by FORMAT. */
 int
-fmt_var_width (const struct fmt_spec *format)
+fmt_var_width (struct fmt_spec format)
 {
-  return (format->type == FMT_AHEX ? format->w / 2
-          : format->type == FMT_A ? format->w
+  return (format.type == FMT_AHEX ? format.w / 2
+          : format.type == FMT_A ? format.w
           : 0);
 }
 
@@ -482,23 +594,23 @@ fmt_var_width (const struct fmt_spec *format)
    even if F's format type does not allow decimals, to allow
    accurately presenting incorrect formats to the user. */
 char *
-fmt_to_string (const struct fmt_spec *f, char buffer[FMT_STRING_LEN_MAX + 1])
+fmt_to_string (struct fmt_spec f, char buffer[FMT_STRING_LEN_MAX + 1])
 {
-  if (fmt_takes_decimals (f->type) || f->d > 0)
+  if (fmt_takes_decimals (f.type) || f.d > 0)
     snprintf (buffer, FMT_STRING_LEN_MAX + 1,
-              "%s%d.%d", fmt_name (f->type), f->w, f->d);
+              "%s%d.%d", fmt_name (f.type), f.w, f.d);
   else
     snprintf (buffer, FMT_STRING_LEN_MAX + 1,
-              "%s%d", fmt_name (f->type), f->w);
+              "%s%d", fmt_name (f.type), f.w);
   return buffer;
 }
 
 /* Returns true if A and B are identical formats,
    false otherwise. */
 bool
-fmt_equal (const struct fmt_spec *a, const struct fmt_spec *b)
+fmt_equal (struct fmt_spec a, struct fmt_spec b)
 {
-  return a->type == b->type && a->w == b->w && a->d == b->d;
+  return a.type == b.type && a.w == b.w && a.d == b.d;
 }
 
 /* Adjusts FMT to be valid for a value of the given WIDTH if necessary.
@@ -927,22 +1039,18 @@ fmt_from_u32 (uint32_t u32, int width, bool loose, struct fmt_spec *f)
   uint8_t w = u32 >> 8;
   uint8_t d = u32;
 
-  msg_disable ();
-  f->w = w;
-  f->d = d;
-  bool ok = fmt_from_io (raw_type, &f->type);
-  if (ok)
-    {
-      if (loose)
-        fmt_fix_output (f);
-      else
-        ok = fmt_check_output (f);
-    }
-  if (ok)
-    ok = fmt_check_width_compat (f, width);
-  msg_enable ();
+  enum fmt_type type;
+  if (!fmt_from_io (raw_type, &type))
+    return false;
+
+  *f = (struct fmt_spec) { .type = type, .w = w, .d = d };
 
-  return ok;
+  if (loose)
+    fmt_fix_output (f);
+  else if (!fmt_check_output (*f))
+    return false;
+
+  return fmt_check_width_compat (*f, width);
 }
 
 /* Returns true if TYPE may be used as an input format,
@@ -1144,12 +1252,8 @@ fmt_clamp_width (struct fmt_spec *fmt, enum fmt_use use)
 static void
 fmt_clamp_decimals (struct fmt_spec *fmt, enum fmt_use use)
 {
-  int max_d;
-
-  max_d = fmt_max_decimals (fmt->type, fmt->w, use);
-  if (fmt->d < 0)
-    fmt->d = 0;
-  else if (fmt->d > max_d)
+  int max_d = fmt_max_decimals (fmt->type, fmt->w, use);
+  if (fmt->d > max_d)
     fmt->d = max_d;
 }
 \f
@@ -1166,7 +1270,7 @@ fmt_affix_clone (const struct fmt_affix *old)
 static void
 fmt_affix_free (struct fmt_affix *affix)
 {
-  if (affix->s[0])
+  if (affix->s)
     free (affix->s);
 }
 
@@ -1235,6 +1339,7 @@ fmt_number_style_from_string (const char *s)
     .neg_suffix = neg_suffix,
     .decimal = grouping == '.' ? ',' : '.',
     .grouping = grouping,
+    .include_leading_zero = false,
     .extra_bytes = extra_bytes,
   };
   return style;
@@ -1334,7 +1439,7 @@ get_fmt_desc (enum fmt_type type)
   return &formats[type];
 }
 
-const struct fmt_spec F_8_0 = {FMT_F, 8, 0};
-const struct fmt_spec F_8_2 = {FMT_F, 8, 2};
-const struct fmt_spec F_4_3 = {FMT_F, 4, 3};
-const struct fmt_spec F_5_1 = {FMT_F, 5, 1};
+const struct fmt_spec F_8_0 = { .type = FMT_F, .w = 8, .d = 0 };
+const struct fmt_spec F_8_2 = { .type = FMT_F, .w = 8, .d = 2 };
+const struct fmt_spec F_4_3 = { .type = FMT_F, .w = 4, .d = 3 };
+const struct fmt_spec F_5_1 = { .type = FMT_F, .w = 5, .d = 1 };