在 Python 中交错两个十进制数字
Posted
技术标签:
【中文标题】在 Python 中交错两个十进制数字【英文标题】:Interleaving two decimal digits in Python 【发布时间】:2021-01-23 14:35:01 【问题描述】:我对所谓的“交错函数”f 的高效 Python 实现感兴趣,它采用 (0,1) 中的两个数字 a、b 并将它们的十进制数字交错,即
f(a,b) := 0.a1 b1 a2 b2 a3 b3 ... 其中 a = 0.a1 a2 a3... 和 b = 0.b1 b2 b3... 是 a 的十进制表示,b.
从数学上讲,函数 f 是从 (0,1)x(0.1) 到 (0,1) 的一对一映射。
您能否建议如何在 Python 中有效地实现此映射以保持它是一对一的?
【问题讨论】:
这会给你足够的信息来产生你自己的答案:How to take the nth digit of a number in python 谢谢,@mapto。虽然我仍然不确定 f 的简单实现是否保留了单射性:如果我将两个“十进制字符串”a 和 b 传递给 f,它将返回长度为 |c| 的十进制字符串 c = f(a,b) = |一个| + |b| (|a| a 的长度)。仅当字符串 c 是“a 和 b 的完整(交错)连接”时,此过程才会是一对一的(即,仅当 a 或 b 中的字母在交错后没有丢失时);这可以在 Python 中得到保证吗? 我正要为你实现它,但对我来说似乎有些模棱两可:例如 if |a| = 2*|b|,前 2*|a|数字是交错的。当 b 中没有更多对应的数字时,你将如何交错 a 的后半部分? 我想你说的是|c| = |一个| + |b|是不精确的,因为“0”。部分不会重复,因此 c 的符号将少于 a 和 b 的组合。 你说得对,@mapto,我对“长度”|a| 的定义不够精确。实际上定义了 f 的参数 a ,对此感到抱歉。严格来说,每个数字(a 或 b)都有无限多的十进制数字(使我上面关于“长度”的概念毫无意义),尽管如果 a 和 b 是有理数(如机器数的情况),这些数字中只有有限的许多数字会非零。在这种情况下,我希望下面的例子能说明我上面的评论可以如何以一种有意义的方式解释: 【参考方案1】:对于高效的实现,需要确保实现两件事:在big O notation 方面的渐近复杂度最小,以及高效的计算运算符,避免重复或不必要的计算。
鉴于这个问题,它不太可能用一种对输入数字的长度小于线性的算法来解决。在运算符方面,鉴于我们使用十进制格式,我们很难从一些按位(二进制)计算中受益。因此,我们可能最擅长一般数学运算。
使用浮动
第一个简单的实现会尝试在浮点数上执行函数:
def interleave_float(a: float, b: float) -> float:
a_rest = a
b_rest = b
result = 0
dst_pos = 1.0 # position of written digit
while a_rest != 0 or b_rest != 0:
dst_pos /= 10 # move decimal point of write
a_rest *= 10 # move decimal point of read
result += a_rest // 1 * dst_pos
a_rest %= 1 # remove current digit
dst_pos /= 10
b_rest *= 10
result += dst_pos * (b_rest // 1)
b_rest %= 1
return result
但是,一个简单的测试显示了一个问题 - inherently limited precision of floating point arithmetic 在浮点后的第 16-17 位已经失真:
>>> a = 0.987654321
>>> b = 0.1234567890123456789
>>> print(a)
0.987654321
>>> print(f"b:.20") # formatted to show higher precision
0.12345678901234567737
>>> print(f"Float: interleave_float(a, b):.50")
Float: 0.91827364554637280757987127799424342811107635498047
使用小数
克服精度问题的常用方法是使用decimal.Decimal,fixed-point decimal arithmetic的python实现:
from decimal import Decimal, getcontext
getcontext().prec = 50 # increase number precision
def interleave_fixed(a: Decimal, b: Decimal) -> Decimal:
a_rest = a
b_rest = b
result = 0
dst_pos = Decimal(1)
while a_rest != 0 or b_rest != 0:
dst_pos *= Decimal(0.1)
a_rest *= 10 # move decimal point
result += a_rest // 1 * dst_pos
a_rest %= 1 # remove current digit
dst_pos *= Decimal(0.1)
b_rest *= 10
result += dst_pos * (b_rest // 1)
b_rest %= 1
return result
这似乎对 b 更有效,但不幸的是,它也会导致结果中大约相同数字的不精确。计算后上下文中的 Inexact 标志也表明了这种不精确性:
>>> print(getcontext())
Context(prec=50, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, DivisionByZero, Overflow])
>>> a = Decimal(".987654321")
>>> b = Decimal(".1234567890123456789")
>>> print(a)
0.987654321
>>> print(b)
0.1234567890123456789
>>> print(f"Fixed: interleave_fixed(a, b)")
Fixed: 0.91827364554637287146771953200668367263491993253785
>>> print(getcontext())
Context(prec=50, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[Inexact, FloatOperation, Rounded], traps=[InvalidOperation, DivisionByZero, Overflow])
使用 str
另一种不应因精度而施加限制的方法(您自己提出的)是使用字符串进行句法处理:
def interleave_str(a: str, b: str) -> str:
result = "0."
src_pos = 2 # position of read digit
while len(a) > src_pos or len(b) > src_pos:
result += a[src_pos] if len(a) > src_pos else "0"
result += b[src_pos] if len(b) > src_pos else "0"
src_pos += 1
return result[:-1] if result.endswith("0") else result
如果存在则删除 traling 0
该算法不进行验证,因此您可以自行决定可能要添加的内容。然而,对此进行测试可以得到所需的精度:
>>> a = "0.987654321"
>>> b = "0.1234567890123456789"
>>> print(a)
0.987654321
>>> print(b)
0.1234567890123456789
>>> print(f"String: interleave_str(a, b)")
String: 0.91827364554637281900010203040506070809
...但是如何处理生成的字符串?也许再次将其转换为十进制?取决于你想如何使用结果。
【讨论】:
(至于这个函数的可能用法:我本来希望,如你所说,将交错的字符串再次转换为小数,然后用这个小数对其进行一些计算。虽然看起来很难避免舍入错误会扭曲这些计算,更不用说由简单操作(例如(重复)乘以小数)导致的内存溢出。)以上是关于在 Python 中交错两个十进制数字的主要内容,如果未能解决你的问题,请参考以下文章