将浮点型转换为字符串的Numba Python NumPy数组

11

我正在运行一个@nb.njit函数,在这个函数中,我尝试将一个整数放入一个字符串数组中。

import numpy as np
import numba as nb

@nb.njit(nogil=True)
def func():
    my_array = np.empty(6, dtype=np.dtype("U20"))
    my_array[0] = np.str(2.35646)
    return my_array


if __name__ == '__main__':
    a = func()
    print(a)

我得到了以下错误信息:
numba.errors.TypingError: Failed in nopython mode pipeline (step: nopython frontend)
Invalid use of Function(<class 'str'>) with argument(s) of type(s): (float64)

我该使用哪个函数在 numba 中将 float 转换为 string

2
你可能想在 https://github.com/numba/numba/issues/ 上提出这个问题。 - Dominik Stańczak
1
你使用字符串数组的原因是什么?当你只需要将浮点数转换为字符串进行插入时,我很好奇为什么不直接使用float64 dtype的数组。 - Matt Eding
我在实际应用中有其他元素(日期),将它们全部作为字符串处理更简单。最终我确实将它们全部作为浮点数处理了。 - Chapo
5个回答

4
numpy.str函数目前不受支持。可以在Numba网站上找到所有支持的numpy函数列表。(点击此处) 内置的str也没有得到支持。可以在支持的Python功能页面中进行检查。
唯一的解决方案是,通过使用Python和Numpy支持的特性,创建一个将浮点数转换为字符串的函数。
在这个方向之前,需要重新考虑将浮点数转换为字符串的必要性。这可能不是很高效,而且可能会因为将浮点数转换为字符串而增加一些额外开销,从而失去了编译一些函数的好处。
当然,如果没有更多项目信息,很难做出判断。

2
提供了一种相对通用的方法:
import math
import numba as nb


@nb.njit
def cut_trail(f_str):
    cut = 0
    for c in f_str[::-1]:
        if c == "0":
            cut += 1
        else:
            break
    if cut == 0:
        for c in f_str[::-1]:
            if c == "9":
                cut += 1
            else:
                cut -= 1
                break
    if cut > 0:
        f_str = f_str[:-cut]
    if f_str == "":
        f_str = "0"
    return f_str


@nb.njit
def float2str(value):
    if math.isnan(value):
        return "nan"
    elif value == 0.0:
        return "0.0"
    elif value < 0.0:
        return "-" + float2str(-value)
    elif math.isinf(value):
        return "inf"
    else:
        max_digits = 16
        min_digits = -4
        e10 = math.floor(math.log10(value)) if value != 0.0 else 0
        if min_digits < e10 < max_digits:
            i_part = math.floor(value)
            f_part = math.floor((1 + value % 1) * 10.0 ** max_digits)
            i_str = str(i_part)
            f_str = cut_trail(str(f_part)[1:max_digits - e10])
            return i_str + "." + f_str
        else:
            m10 = value / 10.0 ** e10
            exp_str_len = 4
            i_part = math.floor(m10)
            f_part = math.floor((1 + m10 % 1) * 10.0 ** max_digits)
            i_str = str(i_part)
            f_str = cut_trail(str(f_part)[1:max_digits])
            e_str = str(e10)
            if e10 >= 0:
                e_str = "+" + e_str
            return i_str + "." + f_str + "e" + e_str

这种方法比纯Python不够精确,而且速度相对较慢(大约慢了3倍):

numbers = (
    math.nan, math.inf, -math.inf, 0.0, 1.0, 1.000001, -2000.000014, 1234567890.12345678901234567890,
    1234567890.12345678901234567890e10, 1234567890.12345678901234567890e-30,
    1.234e-200, 1.234e200
)
k = 32
for number in numbers:
    print(f"{number!r:{k}}  {str(number)!r:{k}}  {float2str(number)!r:{k}}")
# nan                               'nan'                             'nan'                           
# inf                               'inf'                             'inf'                           
# -inf                              '-inf'                            '-inf'                          
# 0.0                               '0.0'                             '0.0'                           
# 1.0                               '1.0'                             '1.0'                           
# 1.000001                          '1.000001'                        '1.000001'                      
# -2000.000014                      '-2000.000014'                    '-2000.0000139'                 
# 1234567890.1234567                '1234567890.1234567'              '1234567890.123456'             
# 1.2345678901234567e+19            '1.2345678901234567e+19'          '1.234567890123456e+19'         
# 1.2345678901234568e-21            '1.2345678901234568e-21'          '1.234567890123457e-21'         
# 1.234e-200                        '1.234e-200'                      '1.234e-200'                    
# 1.234e+200                        '1.234e+200'                      '1.2339e+200'            

%timeit -n 10 -r 10 [float2str(x) for x in numbers]
# 10 loops, best of 10: 18.1 µs per loop
%timeit -n 10 -r 10 [str(x) for x in numbers]
# 10 loops, best of 10: 6.5 µs per loop

但在Numba中本地实现str()用于浮点参数之前,它可以用作解决方法。

请注意,边缘情况处理相对粗略,并且最后1-2位数字的误差相对频繁。


1
这是一个精美编码、稳健可靠的方法。谢谢! - Jean Lescut

1
我想要一个将浮点数转换为字符串的方法,返回一个带有两位小数的字符串,例如“23.45”。 我的解决方案是这样的。也许它能帮助到某些人。
    def floatToString(self, floatnumber:float32) -> str:
        stringNumber:str = ""
        whole:int = math.floor(floatnumber)
        frac:int = 0
        digits:float = float(floatnumber % 1)
        digitsTimes100:float = float(digits) * float(100.0)
        if digitsTimes100 is not None:
            frac = math.floor(digitsTimes100)
        stringNumber = str(whole)+"."+str(frac)
        return stringNumber

要注意舍入问题,但对我来说已经足够了。


这在Numba上能用吗? - norok2

0

虽然这并没有涉及到浮点数转字符串的问题,但我认为值得一提的是,在较新版本的Numba中,您可以在整数上使用str()函数:

import numba as nb


@nb.njit
def int2str(value):
    return str(value)


n = 10
print([int2str(x) for x in range(-n, n)])
# ['-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']

n = 1000
%timeit [str(x) for x in range(-n, n)]
# 1000 loops, best of 5: 391 µs per loop
%timeit [int2str(x) for x in range(-n, n)]
# 1000 loops, best of 5: 813 µs per loop

这比纯Python慢,但可以确保njit()加速。

浮点数的相同功能尚未实现。


0
这里是一个简单的尝试:
  1. 更简单(如果对你来说很重要)
  2. 可以改变精度
  3. 处理NaN值
  4. 在第一个小数为0时工作(例如0.05)
  5. 使用Numba编译
@nb.jit
def f2s(f, precision=2) :
    if np.isnan(f) :
        return 'NaN'
    s = str(int(np.floor(f))) + '.'
    digits = f%1
    for _ in range(precision) :
        digits *= 10
        s += str(int(np.floor(digits)))
    return s

请随意将 int(np.floor(_)) 替换为 math.floor(_),如果你愿意的话。
请参考 @norok2 的答案,那个版本更加健壮/复杂(包含科学计数法等)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接