Set Precision and Clip Trailing Zeros but Never Print Exponent

荒凉一梦 提交于 2019-11-28 12:41:38

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';
}
Lee

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.

Paolo Rossi

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);
...

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);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!