1

I am looking to use sprintf to format some float values but I can't seem to get the formatting right. I have tried "%1.6f" as a format string but this isn't what I want. The "g" specifier with significant digits also doesn't seem to help with other situations where I need the padding.

"1.6f"
Actual output: 1.000000
Expected Output: 1

"g"
Actual output: 1.1
Expected Output: 1.100000

Amal K
  • 4,359
  • 2
  • 22
  • 44
mzkoops56
  • 11
  • 1
  • To me (due to the way floating point number work on computers), `1` and `1.000` convey different information: `1` exactly `1` no question about it; `1.000` some number near `1` to 3dp but not exactly `1`. Now add [Is floating point math broken?](https://stackoverflow.com/questions/588004/is-floating-point-math-broken) and the chance of getting an integer is quite small. Best chance of getting what you want is to print to a buffer and inspect the contents, modify the buffer to be your required representation. – Richard Critten Jul 18 '21 at 11:58
  • You want the `"%g"` format specifier... – David C. Rankin Jul 18 '21 at 12:17
  • 1
    @RichardCritten "chance of getting an integer is quite small" --> Not that small, 40+% of all `float` are whole numbers. – chux - Reinstate Monica Jul 18 '21 at 16:59
  • @chux-ReinstateMonica Assuming 32-bit integers and floats. Then any float less than 1 or greater than 2^32 can not have an integer representation and I submit this is much larger than 40% of all floats. – Richard Critten Jul 18 '21 at 17:50
  • 1
    @RichardCritten OP's question is about _whole numbers_: "... unless a float is whole?". In that context [_integer_](https://en.wikipedia.org/wiki/Integer) in not limited to 32-bit _int_, IAC, for common [`float`](https://en.wikipedia.org/wiki/Single-precision_floating-point_format) 1,778,384,894 are whole numbers. About 3% of `float` have 32-bit `int` values. – chux - Reinstate Monica Jul 18 '21 at 18:10

3 Answers3

2

There is no standard specifier for that, but glibc supports custom specifiers. Here's an example implementation:

#include <printf.h>

#include <cmath>
#include <cstdio>

int double_format(FILE *stream, const printf_info *info,
                  const void *const *args) {
  const double d = **reinterpret_cast<double const *const *>(args);

  if (std::trunc(d) == d) {
    return std::fprintf(stream, "%g", d);
  }

  int width = info[0].width;

  if (width == -1) width = 6;  // defaults to 6

  return std::fprintf(stream, "%.*f", width, d);
}

int double_format_arg_info(const struct printf_info *, size_t n, int *argtypes,
                           int *size) {
  if (n > 0) {
    argtypes[0] = PA_DOUBLE;
    size[0] = sizeof(double);
  }
  return 1;
}

int main() {
  register_printf_specifier('F', double_format, double_format_arg_info);
  std::printf("%4F && %4F\n", 1.0, 1.1);
}

Output is 1 && 1.1000.

Demo

Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93
  • `but GCC and Clang support` It's not a feature of a compiler, but of a standard library. The GNU C library supports overriding printf specifier, unrelated to compiler. – KamilCuk Jul 18 '21 at 12:58
2

As there is no print specifier to meet OP's detailed need ("%g" is close), some test is needed.

To meet "unless a float is whole", test if the value is whole with modf() (or modff() in C for float). The function returns the fractional part.

// Illustrative code with printf vs sprintf
void mzkoops56_print(float x) {
  float ipart; 
  if (modf(x, &ipart) == 0.0) {
    printf("%.0f\n", x);
  } else {
    printf("%.6f\n", x);
  }
}

This will print non-whole values like 0.999996, 1.0000004 that are near a whole value with decimals.

mzkoops56_print(nextafterf(1.0,0));
mzkoops56_print(1.0);
mzkoops56_print(nextafterf(1.0,2));
mzkoops56_print(1.1f);

1.000000
1
1.000000
1.100000
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
1

Is there a format string with sprintf that pads unless a float is whole?

No, there is not. You have to write such functionality yourself. The algorithm basically would be:

  • print with %.6f to temporary string
  • check if that string ends with .000000
    • if so, remove .000000
KamilCuk
  • 120,984
  • 8
  • 59
  • 111