Python中与C的strtod等效的实现

7 投票
4 回答
1569 浏览
提问于 2025-04-17 03:09

我正在把一个C++程序的部分代码转换成Python,但在替换C语言的一个函数 strtod 时遇到了一些麻烦。
我处理的字符串包含一些简单的数学表达式,比如“KM/1000.0”。问题是常量和数字混在一起,所以我不能直接使用float()函数。

我该如何写一个Python函数,来模拟 strtod 的功能,既能返回转换后的数字,又能返回下一个字符的位置呢?

4 个回答

0

我会用一种叫做正则表达式的工具来解决这个问题:

import re
mystring = "1.3 times 456.789 equals 593.8257 (or 5.93E2)"
def findfloats(s):
    regex = re.compile(r"[+-]?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b", re.I)
    for match in regex.finditer(mystring):
        yield (match.group(), match.start(), match.end())

这个工具可以在字符串中找到所有的小数,并且把它们和它们的位置一起返回。

>>> for item in findfloats(mystring):
...     print(item)
...
('1.3', 0, 3)
('456.789', 10, 17)
('593.8257', 25, 33)
('5.93E2', 38, 44)
2

你可以创建一个简单的 C 语言的 strtod 包装器:

#include <stdlib.h>

double strtod_wrap(const char *nptr, char **endptr)
{
   return strtod(nptr, endptr);
}

编译时使用:

gcc -fPIC -shared -o libstrtod.dll strtod.c

(如果你使用的是 64 位的 Python,编译器也必须是 64 位的)

然后可以通过 ctypes 在 Python 中调用它(在 Linux 上:把 .dll 改成 .so,在下面的代码中也是这样,这个是在 Windows 上测试的):

import ctypes

_strtod = ctypes.CDLL('libstrtod.dll')
_strtod.strtod_wrap.argtypes = (ctypes.c_char_p, ctypes.POINTER(ctypes.c_char_p))
_strtod.strtod_wrap.restype = ctypes.c_double

def strtod(s):
    p = ctypes.c_char_p(0)
    s = ctypes.create_string_buffer(s.encode('utf-8'))
    result = _strtod.strtod_wrap(s, ctypes.byref(p))
    return result,ctypes.string_at(p)

print(strtod("12.5hello"))

输出结果是:

(12.5, b'hello')

(这并没有看起来那么难,因为我刚学会怎么做,才花了 10 分钟)

关于 ctypes 的一些有用问答

4

我不知道有没有现成的函数可以做到这一点。

不过,使用正则表达式自己写一个其实很简单:

import re

# returns (float,endpos)
def strtod(s, pos):
  m = re.match(r'[+-]?\d*[.]?\d*(?:[eE][+-]?\d+)?', s[pos:])
  if m.group(0) == '': raise ValueError('bad float: %s' % s[pos:])
  return float(m.group(0)), pos + m.end()

print strtod('(a+2.0)/1e-1', 3)
print strtod('(a+2.0)/1e-1', 8)

一个更好的方法可能是先构建一个词法分析器,这样可以先把表达式分解成一个个小的部分(我们称之为“标记”),然后再处理这些标记,而不是直接处理整个字符串。或者更进一步,可以构建一个类似yacc的解析器。

撰写回答