Эквивалент с плавающей запятой strtol () в C


strtol преобразует введенную строку str в длинное значение любой указанной базы от 2 до 36. strtof() предлагает аналогичную функциональность, но не позволяет вам указывать базу. Есть ли другая функция, которая делает то же самое, что и strtof, но позволяет выбрать базу?

Например, пусть 101.101 вводится в виде строки. Я хочу быть в состоянии сделать
strtof("101.101", null, 2);

И получить на выходе 5.625.

3 2

3 ответа:

Вы можете разобрать строку, чтобы разделить ее на . и преобразовать часть до и часть после в десятичную дробь. После этого вы можете создать поплавок из этой строки. Вот простая функция, которая выполняет эту задачу.

float new_strtof(char* const ostr, char** endptr, unsigned char base)
{
    char* str = (char*)malloc(strlen(ostr) + 1);
    strcpy(str, ostr);
    const char* dot = ".";

    /* I do not validate any input here, nor do I do anything with endptr */      //Let's assume input of 101.1101, null, 2 (binary)
    char *cbefore_the_dot = strtok(str, dot); //Will be 101
    char *cafter_the_dot = strtok(NULL, dot); //Will be 0101

    float f = (float)strtol (cbefore_the_dot, 0, base); //Base would be 2 = binary. This would be 101 in decimal which is 5
    int i, sign = (str[0] == '-'? -1 : 1);
    char n[2] = { 0 }; //will be just for a digit at a time

    for(i = 0 ; cafter_the_dot[i] ; i++) //iterating the fraction string
    {
        n[0] = cafter_the_dot[i];
        f += strtol(n, 0, base) * pow(base, -(i + 1)) * sign; //converting the fraction part
    }

    free(str);
    return f;
}
Можно было бы сделать это более эффективным и менее грязным способом, но это всего лишь пример, чтобы показать вам идею, стоящую за этим. Вышесказанное хорошо работает для меня.

Не забудьте #include <math.h> и скомпилировать с флагом -lm. Примером может служить gcc file.c -o file -lm.

Вычисления с FP могут нести накопленные ошибки округления и другие тонкости. Ниже просто вычисляется целая числовая часть и дробная часть в виде 2 целых чисел с основанием n, а затем, с минимальным вычислением FP, выводится ответ.

Код также должен справиться с отрицательной целой числовой частью и обеспечить, чтобы дробная часть обрабатывалась с тем же знаком.
#include <ctype.h>
#include <math.h>
#include <stdlib.h>

double CC_strtod(const char *s, char **endptr, int base) {
  char *end;
  if (endptr == NULL) endptr = &end;
  long ipart = strtol(s, endptr, base);
  if ((*endptr)[0] == '.') {
    (*endptr)++;
    char *fpart_start = *endptr;
    // Insure `strtol()` is not fooled by a space, + or - 
    if (!isspace((unsigned char) *fpart_start) && 
        *fpart_start != '-' && *fpart_start != '+') {
      long fpart = strtol(fpart_start, endptr, base);
      if (ipart < 0) fpart = -fpart;
      return fma(fpart, pow(base, fpart_start - *endptr), ipart);
    }
  }
  return ipart;
}

int main() {
  printf("%e\n", CC_strtod("101.101", NULL, 2));
}

Вывод

5.625000e+00

Вышеизложенное ограничено тем, что обе части не должны выходить за пределы диапазона long. Код может использовать более широкие типы, такие как intmax_t для менее ограничительной функции.

Для сравнения, вот простая, прямолинейная версия atoi(), которая принимает произвольную базу для использования (т. е. не обязательно 10):

#include <ctype.h>

int myatoi(const char *str, int b)
{
    const char *p;
    int ret = 0;
    for(p = str; *p != '\0' && isspace(*p); p++)
        ;
    for(; *p != '\0' && isdigit(*p); p++)
        ret = b * ret + (*p - '0');
    return ret;
}

(Обратите внимание, что я опустил обработку отрицательных чисел.)

После того, как вы получили это, легко обнаружить десятичную точку и обрабатывать цифры справа от нее:

double myatof(const char *str, int b)
{
    const char *p;
    double ret = 0;
    for(p = str; *p != '\0' && isspace(*p); p++)
        ;
    for(; *p != '\0' && isdigit(*p); p++)
        ret = b * ret + (*p - '0');

    if(*p == '.')
        {
        double fac = b;
        for(p++; *p != '\0' && isdigit(*p); p++)
            {
            ret += (*p - '0') / fac;
            fac *= b;
            }
        }

    return ret;
}

Несколько менее очевидный подход, который может быть численно более эффективным, состоит в следующем:

double myatof2(const char *str, int b)
{
    const char *p;
    long int n = 0;
    double denom = 1;
    for(p = str; *p != '\0' && isspace(*p); p++)
        ;
    for(; *p != '\0' && isdigit(*p); p++)
        n = b * n + (*p - '0');

    if(*p == '.')
        {
        for(p++; *p != '\0' && isdigit(*p); p++)
            {
            n = b * n + (*p - '0');
            denom *= b;
            }
        }

    return n / denom;
}

Я проверил их с помощью

#include <stdio.h>

int main()
{
    printf("%d\n", myatoi("123", 10));
    printf("%d\n", myatoi("10101", 2));

    printf("%f\n", myatof("123.123", 10));
    printf("%f\n", myatof("101.101", 2));

    printf("%f\n", myatof2("123.123", 10));
    printf("%f\n", myatof2("101.101", 2));

    return 0;
}

Который отпечатки

123
21
123.123000
5.625000
123.123000
5.625000

Как и ожидалось.

Еще одно замечание: эти функции не обрабатывают базы больше 10.