форматирование sprintf: фиксированное количество символов, переменные числа десятичных дробей - PullRequest
0 голосов
/ 14 ноября 2018

Я ищу для sprintf -подобной строки формата, которая бы форматировала числа с плавающей запятой с переменной числом десятичных знаков, но фиксированной общим числом символов (скажем, 3), чтобы предоставить максимум информации. Например:

0      ->  `.00` or `0.0` or `  0`
0.124  ->  `.12`
0.357  ->  `.36`
1.788  ->  `1.8`
9.442  ->  `9.4`
10.25  ->  `10.` or ` 10`
75.86  ->  `76.` or ` 76`
99.44  ->  `99.` or ` 99`
100.0  ->  `100`

(да, все мои числа будут с плавающей запятой от 0 до 100)

Как этого добиться?
Реализован ли этот вид форматирования с фиксированной шириной на строковом языке формата sprintf?

Ответы [ 2 ]

0 голосов
/ 14 ноября 2018

Реализован ли этот тип форматирования с фиксированной шириной на строковом языке формата sprintf?

номер

Как этого добиться?

Для достижения целей ОП код может проанализировать диапазон значений float f, чтобы попытаться напечатать с максимальной информацией.

Это начинается с "%.*f" для управления количеством цифр после ..

Обычная ошибка возникает при значениях, меньших степени 10, которые округляются и вызывают появление другой цифры на выходе.

Код может попытаться проверить, например, f < 0.995f, а не f < 1.0, но это приводит к неудачным угловым случаям, учитывая двоичную природу числа с плавающей точкой.

Лучше проверить диапазон по точной константе, такой как 1,0, 10,0, ...

Нижеследующее пытается максимально дважды sprintf(). Вторая попытка происходит с этими противными значениями, находящимися под степенью 10.

#include <assert.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>

void iago_sprint1(char *dest, int len, float f) {
  if (f < 1.0) {
    char buf[len + 2];
    //printf("p1\n");
    snprintf(buf, sizeof buf, "%.*f", len - 1, f);
    if (buf[0] == '0') {
      strcpy(dest, buf + 1);
      return;
    }
  }
  float limit = 10.0;
  int prec = len - 2;

  while (prec >= -1) {
    if (f < limit) {
      char buf[len + 2];
      //printf("p2\n");
      int cnt = snprintf(buf, sizeof buf, "%.*f", prec < 0 ? 0: prec, f);
      if (cnt <= len) {
        strcpy(dest, buf);
        return;
      }
    }
    prec--;
    limit *= 10;
  }
  assert(0); // `f` was out of range
}

#define N 3
int main(void) {
  float f[] = {0, 0.124f, 0.357f, 1.788f, 9.442f, 10.25f, 75.86f, 99.44f,
      100.0f, 99.999f, 9.9999f, .99999f, .099999f, 1.04f, 1.06f};
  char line[N + 1];

  for (unsigned i = 0; i < sizeof f / sizeof *f; i++) {
    //snprintf(line, sizeof line, "%#*g", N, f[i]);
    //puts(line);

    iago_sprint1(line, N, f[i]);
    puts(line);
  }
}

выход

.00
.12
.36
1.8
9.4
10
76
99
100
100
10
1.0
.10
1.0
1.1

Предварительный расчет точности требует ловушки

Если код попытается предварительно вычислить точность, необходимую для только для вызова 1 sprintf(), вычисление должно вывести точность точно так же, как sprintf() - в действительности код выполняет sprint () `work снова.

Рассмотрим len==3 и float x = 9.95f;. Поскольку двоичный файл float не отражает это точно, он вместо этого имеет значение чуть выше или ниже 9,95 1 . Если это ниже, строка должна быть "9.9", если это выше, "10.". Если бы код имел double x = 9.95; (опять же не совсем представимое), вывод может отличаться. Если код использовал float, но FLT_EVAL_MODE > 1, фактическое переданное значение может не соответствовать ожидаемому float 9.95.

Precision-->  .1   .0 
9.94001...   "9.9"  "10."
9.94999...   "9.9"  "10." // Is this 9.95f
9.95001...   "10.0" "10." // or this?
9.95999...   "10.0" "10."

Новое и улучшенное

Я переработал и упростил код - после принятия.

Хитрость заключается в печати с "%*.f" расчетной точностью, основанной на степени 10 f, в буфер на единицу шире, чем цель - при условии, что нет перенести в другое число из-за округления.

Вычисление степени 10 можно выполнить точно с помощью небольшого цикла.

Когда ведущим символом является 0, он не нужен, как в "0.xx" -> ".xx".

В противном случае без переноса в другую цифру из-за округления, строка подходит, и мы закончили.

В противном случае перенос, последний символ будет либо '.', либо '0' после десятичной точки и поэтому не требуется. Это происходит, когда f чуть ниже степени 10, но печатная версия округляется до этой степени 10. И поэтому копируются только цифры длиной 1.

// `len` number of characters to print
// `len+1` is the size of `dest`
void iago_sprint3(char *dest, int len, float f) {
  assert(len >= 1);
  int prec = len - 1;
  float power10 = 1.0;
  while (f >= power10 && prec > 0) {
    power10 *= 10;
    prec--;
  }

  char buf[len + 2];
  int cnt = snprintf(buf, sizeof buf, "%.*f", prec, f);
  assert (cnt >= 0 && cnt <= len + 1);
  if (buf[0] == '0') {
      strcpy(dest, buf + 1);
      return;
    }
  strncpy(dest, buf, (unsigned) len);
  dest[len] = 0;
}

1 Типичный float 9.95f точно

9.94999980926513671875    

Типичный double 9.95 точно

9.949999999999999289457264239899814128875732421875
0 голосов
/ 14 ноября 2018

Вы можете попробовать:

void mysprintf(float a) {
    if (a < 10 && a >= 0) {
        printf("%2f", a);
    } else if (a >= 10 && a < 100) {
        printf("%1f", a);

    } else {
        printf("%d", (int)a);
    }
}
...