2

我有一个 char 数组,一个巨大的数组 char p[n] 从 txt 中读取。

//1.txt
194.919 -241.808 234.896
195.569 -246.179 234.482
194.919 -241.808 234.896
...

foo(char *p, 浮动 x, 浮动 y, 浮动 z) {

}

我尝试使用 atof、strtod,但是当数组太大时它们会非常耗时,因为它们会调用 strlen()。而且 sscanf 也很慢....

调试了代码,发现 atof() 和 strtod 都调用了Visual Studio中的 strlen() ,我们可以查看 crt 代码。

strtod() call:
        answer = _fltin2( &answerstruct, ptr, (int)strlen(ptr), 0, 0, _loc_update.GetLocaleT());


atof() call:
        return( *(double *)&(_fltin2( &fltstruct, nptr, (int)strlen(nptr), 0, 0, _loc_update.GetLocaleT())->dval) );

我也尝试使用strtok,但我们不应该更改1.txt中的任何数据。

所以任何人都有将所有这些转换为浮点 x、y、z 的最佳方法。

视觉工作室 2008 + WIN7

4

10 回答 10

1

好的,如何自己进行标记化,然后调用 strtod。

我在想的是这样的:

char *current = ...;  // initialited to the head of your character array
while (*current != '\0')
{
    char buffer[64];
    unsigned int idx = 0;

    // copy over current number
    while (*current != '\0' && !isspace(*current))
    {
        buffer[idx++] = *current++;
    }
    buffer[idx] = '\0';

    // move forward to next number
    while (*current != '\0' && isspace(*current))
    {
        current++;
    }

    // use strtod to convert buffer   
}

与此相关的一些问题是标记化非常简单。它适用于您发布的格式,但如果格式不同(另一行使用 : 分隔数字),它将不起作用。

另一个问题是代码假设所有数字的长度都小于 64 个字符。如果它们更长,您将获得缓冲区溢出。

此外,复制到临时缓冲区会增加一些开销(但希望少于在整个缓冲区上不断执行 strlen 的开销)。我知道你说你不能改变原来的缓冲区,但你能做一个临时的改变吗(即缓冲区可以改变,只要你在返回之前将它恢复到原来的状态):

char *current = ...;  // initialited to the head of your character array
while (*current != '\0')
{
    char *next_sep = current;
    while (*next_sep != '\0' && !isspace(*next_sep))
    {
        next_sep++;
    }

    // save the separator before overwriting it
    char tmp = *next_sep;
    *next_sep = '\0';

    // use strtod on current

   // Restore the separator.
   *next_sep = tmp;

    current = next_sep;

    // move forward to next number
    while (*current != '\0' && isspace(*current))
    {
        current++;
    }
}

这种技术意味着无需复制,也无需担心缓冲区溢出。您确实需要临时修改缓冲区;希望那是

于 2010-01-09T17:16:15.100 回答
1

查看此代码。

如果不需要支持科学表示、“+”号或前导标签,则可以进一步优化。

它不使用 strlen 或任何其他标准库字符串例程。

// convert floating-point value in string represention to it's numerical value
// return false if NaN
// F is float/double
// T is char or wchar_t
// '1234.567' -> 1234.567
template <class F, class T> inline bool StrToDouble(const T* pczSrc, F& f)
{
    f= 0;

    if (!pczSrc)
        return false;

    while ((32 == *pczSrc) || (9 == *pczSrc))
        pczSrc++;

    bool bNegative= (_T('-') == *pczSrc);

    if ( (_T('-') == *pczSrc) || (_T('+') == *pczSrc) )
        pczSrc++;

    if ( (*pczSrc < _T('0')) || (*pczSrc > _T('9')) )
        return false;

    // todo: return false if number of digits is too large

    while ( (*pczSrc >= _T('0')) && (*pczSrc<=_T('9')) )
    {
        f= f*10. + (*pczSrc-_T('0'));
        pczSrc++;
    }

    if (_T('.') == *pczSrc)
    {
        pczSrc++;

        double e= 0.;
        double g= 1.;

        while ( (*pczSrc >= _T('0')) && (*pczSrc<=_T('9')) )
        {
            e= e*10. + (*pczSrc-_T('0'));
            g= g*10.                    ;
            pczSrc++;
        }

        f+= e/g;
    }

    if ( (_T('e') == *pczSrc) || (_T('E') == *pczSrc) ) // exponent, such in 7.32e-2
    {
        pczSrc++;

        bool bNegativeExp= (_T('-') == *pczSrc);

        if ( (_T('-') == *pczSrc) || (_T('+') == *pczSrc) )
            pczSrc++;

        int nExp= 0;
        while ( (*pczSrc >= _T('0')) && (*pczSrc <= _T('9')) )
        {
            nExp= nExp*10 + (*pczSrc-_T('0'));
            pczSrc++;
        }

        if (bNegativeExp)
            nExp= -nExp;

        // todo: return false if exponent / number of digits of exponent is too large

        f*= pow(10., nExp);
    }

    if (bNegative)
        f= -f;

    return true;
}
于 2010-01-09T16:04:58.740 回答
1

如果您可以对浮点值的格式做出额外的假设,那么自己解析它们可能会提高性能。

不带指数且不带输入验证的解析' '或分隔值的示例代码:'\n'

float parsef(const char **str)
{
    const char *cc = *str;

    _Bool neg = (*cc == '-');
    if(neg) ++cc;

    float value = 0, e = 1;

    for(; *cc != '.'; ++cc)
    {
        if(*cc == ' ' || *cc == '\n' || !*cc)
        {
            *str = cc;
            return neg ? -value : value;
        }

        value *= 10;
        value += *cc - '0';
    }

    for(++cc;; ++cc)
    {
        if(*cc == ' ' || *cc == '\n' || !*cc)
        {
            *str = cc;
            return neg ? -value : value;
        }

        e /= 10;
        value += (*cc - '0') * e;
    }
}

示例代码:

const char *str = "42 -15.4\n23.001";
do printf("%f\n", parsef(&str));
while(*str++);
于 2010-01-09T16:32:21.483 回答
0

正如其他人所说,我认为您不会比标准库调用做得更好。它们已经存在了很长时间并且经过了高度优化(嗯,它们应该是,至少在良好的实现中)。

也就是说,有些事情我不清楚。您是否将整个文件读入内存,然后将数组转换为另一个数组?如果是这样,您可能需要检查正在运行的系统是否有足够的内存来进行交换。如果您这样做,是否可以在从磁盘读取它们而不是存储它们时一次只转换一行?

你可以考虑多线程你的程序。一个线程从磁盘读取和缓冲行,n 个线程处理行。Dobb 博士的期刊发表了一个很棒的单读/单写无锁队列实现,你可以使用。我在类似的应用程序中使用过它。我的工作线程每个都有一个输入队列,然后读取器线程从磁盘读取数据并以循环方式将它们放入这些队列中。

于 2010-01-09T16:00:40.247 回答
0

怎么样:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

static float frac[] =
{
    0.000,
    0.001,
    0.002,
    ...               // fill in
    0.997,
    0.998,
    0.999,
};

static float exp[] =
{
    1e-38,
    1e-37,
    1e-36,
    ...               // fill in
    1e+36,
    1e+37,
    1e+38,
};

float cvt(char* p)
{
    char* d = strchr(p, '.');   // Find the decimal point.
    char* e = strchr(p, 'e');   // Find the exponent.
    if (e == NULL)
        e = strchr(p, 'E');

    float num = atoi(p);
    if (num > 0) {
        num += frac[atoi(d + 1)];
    } else {
        num -= frac[atoi(d + 1)];
    }
    if (e)
        num *= exp[atoi(e)];
    return num;
}

int main()
{
    char line[100];
    while(gets(line)) {
        printf("in %s, out %g\n", line, cvt(line));
    }
}

应该好到三位有效数字。


编辑:注意大尾数。
再次编辑:和负指数。:-(

于 2010-01-09T16:11:19.333 回答
0

只要您没有使用特别糟糕的标准库(这些时候不可能,它们都很好),就不可能比atof.

于 2010-01-09T15:38:34.660 回答
0

使用strtod. 它几乎肯定不会调用strlen。为什么需要知道输入的长度?它只是跑过前导空格,然后使用尽可能多的对浮点字面量有意义的字符,然后返回一个刚刚过去的指针。您可以看到一个示例实现也许您使用的不是最佳?以下是如何使用的示例strtod

#include <stdio.h>
#include <stdlib.h>
int main() {
    char *p = "1.txt 194.919 -241.808 234.896 195.569 -246.179 234.482 194.919 -241.808 234.896";
    char *end = p;
    char *q;
    double d;
    while(*end++ != ' '); // move past "1.txt"
    do {
        q = end; 
        d = strtod(q, &end);
        printf("%g\n", d);
    } while(*end != '\0');
}

这输出:

194.919
-241.808
234.896
195.569
-246.179
234.482
194.919
-241.808
234.896

在我的机器上。

于 2010-01-09T15:40:37.477 回答
0

我看不出有什么理由strod()应该打电话strlen()。当然可能,但它的规范中没有任何内容需要它,如果它确实需要,我会感到惊讶。而且我会说这strtod()与您获得的一样快,而不是自己编写一些特定于 FPU 处理器的东西。

于 2010-01-09T15:42:39.643 回答
0

为什么你认为 atof,strtod 使用 strlen?我从未实现过它们,但我无法想象他们为什么需要知道输入字符串的长度。这对他们来说毫无价值。根据杰森的回答,我会使用 strtod 。这就是它的用途。

是的,如果您有大量文本,则转换需要一些时间。就是那样子。

于 2010-01-09T15:45:39.403 回答
0

我怀疑是否strlen要花很多钱。

如果您可以利用您的数字落在相对有限的范围内,那么我建议您自己解析它,尽可能少地进行计算,例如:

#define DIGIT(c) ((c)>='0' && (c)<='9')

BOOL parseNum(char* *p0, float *f){
  char* p = *p0;
  int n = 0, frac = 1;
  BOOL bNeg = FALSE;
  while(*p == ' ') p++;
  if (*p == '-'){p++; bNeg = TRUE;}
  if (!(DIGIT(*p) || *p=='.')) return FALSE;
  while(DIGIT(*p)){
    n = n * 10 + (*p++ - '0');
  }
  if (*p == '.'){
    p++;
    while(DIGIT(*p)){
      n = n * 10 + (*p++ - '0');
      frac *= 10;
    }
  }
  *f = (float)n/(float)frac;
  if (bNeg) *f = -*f;
  *p0 = p;
  return TRUE;
}
于 2010-01-10T18:11:39.557 回答