3

I need to:

  1. Set precision so that floats are rounded to the hundredths place ( 0.111 prints as 0.11 )
  2. Clip trailing zeros ( 1.0 prints as 1 )
  3. Never print an exponent ( 1000.1 prints as 1000.1 )

printf( "%.2f\n", input ); // handles 1 and 3 but not 2
printf( "%.2g\n", input ); // handles 1 and 2 but not 3
cout << setprecision( 2 ) << input << endl; // handles 1 and 2 but not 3

Is there a printf or cout option that will let me handle all of these?

Pascal Cuoq
  • 79,187
  • 7
  • 161
  • 281
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • 1
    I don't understand how `printf( "%.2f\n",` fails at point 3. It prints no exponent when I use it (Clang on Mac OS X) to print either 1E267 or 1E-267. – Pascal Cuoq Aug 20 '14 at 11:52
  • @PascalCuoq thanks I had changed the order, and forgot to change the attached code. I've fixed it now. – Jonathan Mee Aug 20 '14 at 12:16
  • 1
    @Jongware I almost did the same thing you did, then I considered that a pure C solution was perhaps acceptable to the OP, even if the question does contain some C++. Now I have written a solution in pure C and your edit makes my solution look off-topic. – Pascal Cuoq Aug 20 '14 at 12:39
  • @Pascal: ... okay, agreed I was too hastely. The OP could have been *just* a bit more clear on this. (I have seen posters complain when asking for C++ and handed a pure C solution, which IMO indeed should be at least as good.) – Jongware Aug 20 '14 at 15:54

4 Answers4

4

The C11 standard says of %f and %F (7.21.6.1:8):

A double argument representing a floating-point number is converted to decimal notation in the style [−]ddd.ddd, where the number of digits after the decimal-point character is equal to the precision specification. If the precision is missing, it is taken as 6; if the precision is zero and the # flag is not specified, no decimal-point character appears. If a decimal-point character appears, at least one digit appears before it. The value is rounded to the appropriate number of digits.

Here is a C snippet that produces what you want in a malloced bloc t, that you need to free up afterwards. If you are writing in C99, a variable-length array can also be considered.

The code below does not introduce any additional approximation to the conversion (if your printf prints the correctly rounded conversion to decimal, so will the code below), and works for all floating-point values.

#include <stdio.h>
#include <stdlib.h>
…
int len = snprintf(0, 0, "%.2f", input);
if (len < 0) fail();
char *t = malloc((size_t)len + 1);
if (!t) fail();
if (sprintf(t, "%.2f", input) < 0) fail();
len--;
if (t[len] == '0') {
  len--;
  if (t[len] == '0') {
    len--;
    if (t[len] == '.') len--;
  }
  t[len + 1] = '\0';
}
Pascal Cuoq
  • 79,187
  • 7
  • 161
  • 281
  • So the only reason for my use of `sprintf` is my need for printing a `float` without exponent. I'm working up some C++ now to accomplish what you're doing with C above. If you feel like doing this in C++ style I'll accept your answer, if not I can post my own answer. – Jonathan Mee Aug 21 '14 at 13:47
  • @JonathanMee Sorry, I cannot provide a C++ answer, but if you needed one you really had no reason to tag your question as “C”. In fact, followers of both these tags (me and Jongware, at least) would be thankful if you avoided tagging your future questions with both tags unless they are **really** both relevant to your question. – Pascal Cuoq Aug 21 '14 at 13:53
  • 1
    It seems that any solution I use for this problem must use a `printf` variant. I wan't event aware of `snprintf`. I had hoped that there would be some other old school `stdio.h` magic that would solve this problem without me having to write code to trim the trailing 0s. It doesn't look like that's the case, but that was my rationale for using the `C` tag. – Jonathan Mee Aug 21 '14 at 19:23
  • @JonathanMee: did you see the pretty awesome answers (at least some of them) to [How to implement "char *ftoa(float num" without sprintf() library function](http://stackoverflow.com/questions/2302969/how-to-implement-char-ftoafloat-num-without-sprintf-library-function-i)? You can probably adjust one of them to your taste. – Jongware Aug 21 '14 at 22:28
  • 1
    @Jongware An elegant, minimalistic, correctly rounded solution (unlike all the dubious answers from the question you link to) can be found in the musl library: http://git.musl-libc.org/cgit/musl/tree/src/internal/floatscan.c – Pascal Cuoq Aug 21 '14 at 22:39
  • @Pascal: that's even better! I intended to find a library implementation but .. well .. the first hit *was* that SO answer .. (Are the answers really that bad? (*Some* are, I can see that.)) – Jongware Aug 21 '14 at 23:02
  • @Jongware The conversion of binary floating-point to and from decimal has, unsurprisingly, been studied for years (decades now). The standard references are a bit dated in the vocabulary they use and focus on optimizations that remove in simplicity what they add in speed for common cases. Musl is a good source for understandable algorithms, because the main contributor forced himself to re-invent everything by himself before looking at the literature, and another good source is the blog at http://www.exploringbinary.com/ – Pascal Cuoq Aug 21 '14 at 23:13
  • @Jongware What I really want is the opposite of http://stackoverflow.com/questions/2302969/how-to-implement-char-ftoafloat-num-without-sprintf-library-function-i, I'm trying to find an option in functionality that exists within C++ which will solve my problem. I *don't* want to reinvent the wheel. So to me Pascal Quoq's solution is much preferable to any of those solutions. Pascal Cuoq's solution could be improved upon though by using ``'s `FLT_MAX` and the log trick I do in my answer. That would remove the need for `snprintf` and `malloc`. – Jonathan Mee Aug 22 '14 at 11:08
1

I am not aware of any format specifier that will do what you are looking for.

Pre-digesting the values before passing them to separate format specifiers might work. For example:

  • Multiply the original floating point number by 100 and round to the nearest integer
  • Assign to nScaled (int).
  • Assign mod(nScaled,100) to another integer, nFractional.
  • Assign nScaled/100 to another integer, nWhole.

if( nFractional > 0 )
  printf("%d.%d", nWhole, nFractional );
else
  printf("%d", nWhole );

You probably already know this.

David Ranieri
  • 39,972
  • 7
  • 52
  • 94
Lee
  • 21
  • 2
  • 2
    The issues with this are that the multiplication by 100 can be inexact, and that the result does not have to fit an `int` or any available integer type. You might as well erase trailing zeroes and dots from a string to which you have printed the floating-point number with `%f`, at least it will work for all numbers. – Pascal Cuoq Aug 20 '14 at 12:12
  • 1
    Also it produces the wrong result when 1 ≤ nFractional ≤ 9. – Pascal Cuoq Aug 20 '14 at 12:18
0

I thought another option is to use asprintf() function: it dynamically allocate a string of proper length by itself. Once the string is stored trailing zeros/dot could be cut off:

...
float num;
...

char *digits;
int i=asprintf(&digits, "%.2f", num)-1;

for(; digits[i] !='.'; i--)
  if (digits[i] == '0') digits[i] = NULL; else break;

if (digits[i] == '.') digits[i] = NULL;

printf("%s\n", digits);
free(digits);
...
Flexo
  • 87,323
  • 22
  • 191
  • 272
Paolo Rossi
  • 11
  • 1
  • 3
  • This could be workable on a Linux platform. But this is really inferior to @PascalCuoq or my solution because of it's lack of portability. – Jonathan Mee Aug 21 '14 at 19:12
0

Sadly there is no way to force streams to use printf's %f behavior. So the only way to handle this is trimming decimal places manually as necessary. I've added a C++ code sample that handles this:

string fullfloat(static_cast<size_t>(log10(numeric_limits<declval(input)>::max())) + 5U, '\0'); // Adding 1 for the 10s place, 1 for the '.' 2 for the decimal places, and 1 for the null
const size_t length = size(fullfloat) - sprintf(data(fullfloat), "%.2f", input );

*(--mismatch(rbegin(fullfloat) + length, next(rbegin(fullfloat), 3U + length), "00.").first) = '\0';

fullfloat will now contain the correct string, but because it's size will extend past the applied '\0' character the only way to get it to print property would be to use data():

cout << data(fullfloat);
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288