【问题标题】:Floating point equivalent to strtol() in C相当于 C 中的 strtol() 的浮点数
【发布时间】:2017-03-13 06:51:37
【问题描述】:

strtol 将输入的字符串 str 转换为 2 到 36 的任何指定基数的 long 值。strtof() 提供类似的功能,但不允许您指定基数。是否有其他功能与strtof 相同但允许您选择基数?

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

strtof("101.101", null, 2);

得到5.625.的输出

【问题讨论】:

  • 你能展示一个要转换的浮点值的例子吗?
  • 你可以简单地转换'.'之前和之后的部分。使用 strtol 然后从两者中浮出
  • @ZachP 这适用于小数点前的数字,但小数点后的数字呢?
  • @ChristopherClarke 我已经在答案中写了这个,它对我有用

标签: c binary base strtol


【解决方案1】:

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

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

【讨论】:

  • 101.1101 应该是 5.8125,但你会得到 5.13,对吧?也许你可以更清楚一点,二进制分数的表示是什么以及你如何在你的样本中得到小数
  • 你将如何处理小数部分的前导 0?
  • @KarstenKoop 你是对的,会处理分数部分。但是,关于处理前导 0,您是什么意思?它会在哪里引起问题?
  • 数字 1.1 和 1.01 不应导致相同的结果
  • @KarstenKoop 确实,这是分数计算中的问题 - 现在会解决这个问题
【解决方案2】:

使用 FP 进行计算可能会产生累积的舍入误差和其他细微差别。下面简单地将整数部分和小数部分计算为 2 个 base-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,以实现限制较少的功能。

【讨论】:

    【解决方案3】:

    为了比较,这里有一个简单直接的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 的基数。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-03-16
      • 1970-01-01
      • 2010-10-03
      • 1970-01-01
      • 2013-07-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多