相当于 C 中的 strtol() 的浮点数

Floating point equivalent to strtol() in C

strtol 将输入的字符串 str 转换为 2 到 36 之间任何指定基数的长整型值。strtof() 提供类似的功能,但不允许您指定基数。是否有另一个功能与 strtof 相同但允许您 select base?

例如假设 101.101 作为字符串输入。我希望能够做到

strtof("101.101", null, 2);

并得到 5.625.

的输出

您可以解析字符串以在.中将其拆分并将前后部分转换为十进制。之后,您可以从该字符串创建一个浮点数。这是一个完成该操作的简单函数。

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.

为了比较,这里有一个简单直接的 atoi() 版本,它接受任意基数来使用(即不一定是 10):

#include <ctype.h>

int myatoi(const char *str, int b)
{
    const char *p;
    int ret = 0;
    for(p = str; *p != '[=10=]' && isspace(*p); p++)
        ;
    for(; *p != '[=10=]' && 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 != '[=11=]' && isspace(*p); p++)
        ;
    for(; *p != '[=11=]' && isdigit(*p); p++)
        ret = b * ret + (*p - '0');

    if(*p == '.')
        {
        double fac = b;
        for(p++; *p != '[=11=]' && 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 != '[=12=]' && isspace(*p); p++)
        ;
    for(; *p != '[=12=]' && isdigit(*p); p++)
        n = b * n + (*p - '0');

    if(*p == '.')
        {
        for(p++; *p != '[=12=]' && 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 的基数。

使用 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 来实现限制较少的功能。