Python中IEEE 754二进制128的浮点字符串

2024-09-28 23:15:26 发布

您现在位置:Python中文网/ 问答频道 /正文

我想要这样一个函数:

>>> binary128_to_hex("1.0")
'3fff0000000000000000000000000000'

我目前在x86笔记本电脑上使用C和qemu-aarch64来完成这项工作。我如何“本地”实现这样的功能?我发现numpy.float128struct包没有帮助。
此外,多亏了Mark Dickinson’s answer,我想出了进行反向转换的办法(尽管只处理规范化的值):

import decimal


def hex_to_binary128(x: str):
    with decimal.localcontext() as context:
        context.prec = 34
        x = int(x, 16)
        significand_mask = (1 << 112) - 1
        exponent_mask = (1 << 127) - (1 << 112)
        trailing_significand = x & significand_mask
        significand = 1 + decimal.Decimal(trailing_significand) / (1 << 112)
        biased_exponent = (x & exponent_mask) >> 112
        exponent = biased_exponent - 16383
        f = significand * decimal.Decimal(2) ** exponent
        return f


if __name__ == "__main__":
    print(hex_to_binary128("0000ffffffffffffffffffffffffffff"))
    # 3.362103143112093506262677817321752E-4932

Tags: to函数contextmaskx86笔记本电脑decimalhex
1条回答
网友
1楼 · 发布于 2024-09-28 23:15:26

这里有一整套可能的解决方案,这取决于您希望允许的复杂性、您需要的性能、您准备依赖外部库的程度,以及您需要在多大程度上处理IEEE 754特殊情况(溢出、低于正常值、有符号零等)

这里有一些工作代码,我希望能给出一个合理的折衷方案。它(a)相当简单,(b)不依赖于标准库之外的任何东西,(c)处理次正常值、有符号的零和溢出相当好(但不尝试解释像“inf”或“nan”这样的字符串),并且(d)可能具有糟糕的性能。但如果你只对随意使用感兴趣,它可能就足够了

代码的思想是通过使用fractions.Fraction解析器解析Fraction对象的字符串输入来避开所有解析困难。然后,我们可以分离Fraction对象并构造我们需要的信息

我将分三部分介绍解决方案。首先,我们需要的一个基本工具是计算正Fraction的二进制指数(换句话说,base-2 log的下限)的能力。下面是代码:

def exponent(f):
    """
    Binary exponent (IEEE 754 style) of a positive Fraction f.

    Returns the unique integer e such that 2**e <= f < 2**(e + 1). Results
    for negative or zero f are not defined.
    """
    n, d = f.numerator, f.denominator
    e = n.bit_length() - d.bit_length()
    if e >= 0:
        adjust = (n >> e) < d     # n / d < 2**e  <=>  floor(n / 2**e) < d
    else:
        adjust = (-d >> -e) < -n  # n / d < 2**e  <=>  floor(-d / 2**-e) < -n
    return e - adjust

这很简单:分子和分母的位长度之差e要么给我们正确的指数,要么比它应该的大一。为了弄清楚这一点,我们必须将分数的值与2**e进行比较,其中e是我们的测试指数。我们可以直接这样做,将2**e计算为Fraction,然后进行比较,但是使用一些位移位会更有效,所以我们就是这么做的

接下来,我们定义一些基本常量和派生常量,这些常量描述IEEE 754 binary128格式。(通过将这些常量替换为binary64格式的常量并检查结果是否如预期的那样,可以很容易地测试下面的代码。)格式的位宽度为128;精度是113,其他一切都可以从这两个值中推导出来

# Basic and derived constants for the format.
WIDTH = 128
PRECISION = 113
EMAX = (1 << WIDTH - PRECISION - 1) - 1
EMIN = 1 - EMAX
INF = (1 << WIDTH - 1) - (1 << PRECISION - 1)
SIGN_BIT = 1 << WIDTH - 1

这其中大部分应该是不言自明的。常量INF是正无穷大常量的位表示,我们将使用它来处理溢出

最后,这里是主要功能:

from fractions import Fraction as F

def binary128_to_hex(s):
    """
    Convert a decimal numeric string to its binary128 representation.

    Given a decimal string 's' (for example "1.2", or "-0.13e-123"), find the
    closest representable IEEE 754 binary128 float to the value represented
    by that string, and return a hexadecimal representation of the bits
    of that float in the corresponding IEEE 754 interchange format.
    """
    # Convert absolute value to a Fraction. Capture the sign separately.
    f, negative = abs(F(s)), s.lstrip().startswith("-")

    # Find the bits representing the significand and exponent of the result.
    if f == 0:
        bits = 0  # Handle special case of zero.
    else:
        # Find exponent; adjust for possible subnormal.
        exp = max(exponent(f), EMIN)
        if exp > EMAX:
            bits = INF  # Overflow to infinity
        else:
            significand = round(f / F(2) ** (exp + 1 - PRECISION))
            bits = (exp - EMIN << PRECISION - 1) + significand

    # Merge sign bit if necessary, then format as a hex string.
    if negative:
        bits |= SIGN_BIT
    return f'{bits:0{WIDTH//4}x}'

上面有两个狡猾的花招值得特别提及:首先,当从exponentsignificand使用表达式(exp - EMIN << PRECISION - 1) + significand构造bits时,我们不做任何特殊的处理亚正常。尽管如此,代码仍能正确地处理次正常值:对于正常情况,指数值exp - EMIN实际上比它应该的值小一个,但在执行加法时,有效位的最高有效位最终使指数字段递增。(因此,重要的是我们使用+而不是|来组合指数块和有效位。)

另一个观察结果是exp的选择确保了round调用的参数严格小于2**PRECISION,但是round调用的结果可能恰好是2**PRECISION。在这一点上,您可能希望我们必须测试这种情况,并相应地调整指数和有效位。但是,同样地,不需要特别处理这种情况-当使用(exp - EMIN << PRECISION - 1) + significand组合字段时,我们得到了指数字段的额外增量,并且所有结果都正确,即使在最后溢出到无穷大的拐角情况下也是如此。IEEE754二进制交换格式的优雅设计使这种欺骗成为可能

下面是在几个示例上测试上述代码的结果:

>>> binary128_to_hex("1.0")
'3fff0000000000000000000000000000'
>>> binary128_to_hex("-1.0")
'bfff0000000000000000000000000000'
>>> binary128_to_hex("-0.0")
'80000000000000000000000000000000'
>>> binary128_to_hex("3.362103143112093506262677817321752E-4932")
'0000ffffffffffffffffffffffffffff'
>>> binary128_to_hex("1.1897314953572317650857593266280071308E+4932")
'7fff0000000000000000000000000000'
>>> binary128_to_hex("1.1897314953572317650857593266280070162E+4932")
'7ffeffffffffffffffffffffffffffff'
>>> binary128_to_hex("1.2345E-4950")  # subnormal value
'00000000000000000006c5f6731b03b8'
>>> binary128_to_hex("3.14159265358979323846264338327950288")
'4000921fb54442d18469898cc51701b8'

相关问题 更多 >