Julia: 将BigFloat转换为字节数组

5
我想获取一个BigFloat数字的二进制位数。我认为最简单的方法是将数字转换为字节数组。在this question中,有一种方法可以为Float64执行此操作,但当我尝试对BigFloat执行相同的操作时,它告诉我write没有BigFloat的方法。
如何使用Julia实现?
PS. 我知道存在一种获取二进制位数作为字符串的方法,但这会引入巨大的开销,我想避免它。
3个回答

3
序列化或表示BigFloat很棘手,因为:a. BigFloat结构使用指针,b. 指向的缓冲区由GMP库控制,该库是Julia中包装的外部库。
因此,首先需要一些函数以准确且易于操作的方式对BigFloat进行文本格式化:
# numbits(a) returns the number of significant digits in a BigFloat
# including digits both left and right of floating point.
# For example:
#     julia> numbits(BigFloat(3/8)
#     2
#
# This is the trickiest function since it looks slightly into
# the internal representation in GMP of BigInt data.

function numbits(a::BigFloat)
    n = a.prec
    for i=1:(a.prec>>count_ones(sizeof(Base.GMP.Limb)*8-1))
        tz = trailing_zeros(unsafe_load(a.d,i))
        n -= tz
        if tz < sizeof(Base.GMP.Limb)*8
            break
        end
    end
    return n
end

# mantissarep(a) returns a tuple with two elements. The first element is a BigInt
# holding all the significant bits of a BigFloat, and the second holds
# the exponent needed to return the floating point to its position.
# Thus, as an example, the following holds:
#     julia> a = BigFloat(1.1)
#     1.100000000000000088817841...
#
#     julia> mantissarep(a)
#     (2476979795053773, 51)
# 
#     julia> big"2476979795053773"/big"2"^51 == a
#     true
#

mantissarep(a::BigFloat) = (BigInt(a*BigFloat(2)^(numbits(a)-a.exp)),
                            numbits(a)-a.exp)

# bigfloattext(a) returns an easy textual representation of a BigFloat

function bigfloattext(bf::BigFloat)
    (a,b) = mantissarep(bf)
    return "$(sign(a)==-1 ? "-" : "")big\"$(dec(abs(a)))\"/big\"2\"^$(b)"
end

  

# bigfloatbintext(a) returns an easy binary textual representation a BigFloat

function bigfloatbintext(bf::BigFloat)
    (a,b) = mantissarep(bf)
    return "$(sign(a)==-1 ? "-" : "")big\"0b$(bin(abs(a)))\"/big\"2\"^0b$(bin(b))"
end

使用这些函数,将BigFloat序列化的一种方法是简单地编写bigfloattext的字符串输出。这种方法不是非常紧凑,但它将很容易反序列化(可以使用BigFloat的parse方法),并且相对清晰。为了使序列化更加紧凑,可以编写mantissarep的字节和BigFloat的精度字段。如果需要,这会使答案略长,所以我会添加。例如编写:
julia> a = sin(BigFloat(π)/3)    # calculate some BigFloat
8.660254037844386467637231707529361834714026269051903140279034897259665084543988e-01

julia> println(bigfloattext(a))  # represent it as a string
big"100278890836790510567389408543623384672710501789331344711007167057270294106993"/big"2"^256

julia> # show representation is faithful
julia> big"100278890836790510567389408543623384672710501789331344711007167057270294106993"/big"2"^256 == a
true

注意(关于其他答案):写入类型为Ptr {Limb}的BigFloat的d字段(=数据字段)是相当无用的,可能会导致内存损坏。
更新:在发布者的评论指导下,这里提供另外两个将BigFloat转换为字节表示的函数:
function BigFloat2Bytes(bf::BigFloat)
    bf2 = bf/big"2"^exponent(bf)-1.0
    bvec = Vector{UInt8}()
    push!(bvec,0x01)
    while bf2 != 0.0
        bf2 *= 256
        b = trunc(Int, bf2)
        push!(bvec, UInt8(b))
        bf2 -= b
    end
    return (bvec, exponent(bf))
end

function Bytes2BigFloat(bvec, e)
    bf = zero(BigInt)
    for b in bvec
        bf = bf*256 + b
    end
    return BigFloat(bf*BigFloat(2)^(e-8*length(bvec)+8))
end

以下是我们拥有的内容:

julia> a = BigFloat(123.2323)
1.23232299999999995065991242881...e+02

julia> BigFloat2Bytes(a)
(UInt8[0x01, 0xec, 0xed, 0xe0, 0x0d, 0x1b, 0x71, 0x70], 6)

julia> Bytes2BigFloat(BigFloat2Bytes(a)...)
1.23232299999999995065991242881...e+02

julia> Bytes2BigFloat(BigFloat2Bytes(a)...) == a
true

你能解释一下第一个函数是做什么的吗?我真的无法理解它。 - tst
关于乘以2 ^ 8的想法,它应该也能很好地工作。甚至可能很快,因为乘以2 ^ 8只是改变指数。但获取有效部分需要进行一些计算。这归结为执行与“dec(...)”或“bin(...)”函数相同的迭代(将其转换为文本数字)。 - Dan Getz
此外,numbits 返回的是二进制位数(而不是十进制)。 - Dan Getz
添加了使用您建议的逻辑的函数。请在答案中查看。 - Dan Getz
太好了,这正是我想要的。不过,由于bf的精度已知,数组是否可以用其长度进行初始化呢? - tst
显示剩余3条评论

2
我试图使用其他方法。我不确定我是否做得对!但也许这很有趣。:)
我们可以通过使用负责BigFloat(MPFR)的库来获得最小的开销。所以我尝试调用mpfr_sprintf(http://www.mpfr.org/mpfr-current/mpfr.html#Formatted-Output-Functions)来获取BigFloat的十六进制和二进制表示:
julia> begin 
   a = " "^1000  # this could be not enough!
   f = BigFloat(123.2323)
   ccall(
      (:mpfr_sprintf, :libmpfr), 
      Int64, 
      (Cstring, Cstring, Ref{BigFloat}...),
      a, 
      "%Rb\n%Ra", 
      Ref(f), Ref(f))
   res_bin = a[1:search(a, '\n')-1]
   res_hex = a[search(a, '\n')+1:search(a, '\0')-1]
   println(res_bin)
   println(res_hex)
   println(f == BigFloat(res_hex))
   end
1.1110110011101101111000000000110100011011011100010111p+6
0x7.b3b780346dc5cp+4
true

我错过了更好的Julia API(不是ccall)来处理BigFloat中的mpfr_sprintf(和其他函数)吗?
编辑:对tst的一些解释。
抱歉,我认为我的答案对你来说不是最重要的,因为它是关于转换为字符串的。BigFloat基于MPFR库。它有mpfr_sprintf函数,可以将输出格式化为字符串。 "%Ra"是mpfr_t类型的十六进制输出的格式字符串示例(Ref(BigFloat)是此类型的引用)。Julia在调用c库方面很强大,因此您可以调用mpfr_sprintf。我展示了二进制和十六进制输出。输出是以空结束的字符串,因此我需要从结果中解析它。res_hex值的内容->“0x7.b3b780346dc5cp + 4”,您可以将其用于REPL或代码-它支持十六进制浮点格式。
julia> 0x7.b3b780346dc5cp+4
123.2323

julia> typeof(0x7.b3b780346dc5cp+4)
Float64 

“p+4”在这里表示“二进制指数”(顺便说一下,0x7b == 123(来自BigFloat(123.2323)))。
另外,您可以将Dan的答案结果重写为十六进制浮点表示形式->
# [0x01, 0xec, 0xed, 0xe0, 0x0d, 0x1b, 0x71, 0x70], 6) ->
julia> 0x1.ecede00d1b7170p+6
123.2323

顺便提一下,Python 有一个 hex 函数可以返回十六进制表示 -> https://pythonhosted.org/bigfloat/reference/index.html#bigfloat.BigFloat.hex

我其实不知道这里发生了什么。 - tst

1

Dan的回答看起来比我的更加健壮,但对于序列化仍然有些笨拙。

只是作为参考,这里有一个我用来从BigFloat转换为Vector{UInt8}的快速而简单的答案。

function unsafe_serialize(p::BigFloat)
    vcat(
        reinterpret(UInt8, [p.prec]),
        reinterpret(UInt8, [p.sign]),
        reinterpret(UInt8, [p.exp]),
        reinterpret(UInt8, unsafe_wrap(Array, p.d, (4,)))
    )
end

和回来

function unsafe_deserialize(::Type{BigFloat}, p::Array{UInt8})
    Base.MPFR._BigFloat(
        reinterpret(Int64, p[1:8])[1],
        reinterpret(Int32, p[9:12])[1],
        reinterpret(Int64, p[13:20])[1],
        String(p[21:52])
    )
end

这些代码之所以“不规范”,是因为我已经硬编码了数字和类型。在第一个函数中,数字“4”假设你的机器使用Int64作为C语言中的long类型,并且你保留了默认精度(或者稍微降低了一点)。在第二个函数中,每个Int*类型和相应的范围都是依赖于机器的,而最终范围则依赖于精度。这些可以通过使用正确的大小和类型来改进,但我太懒了。
我知道这些在我的机器上使用默认精度可以工作,并且可能适用于大多数安装,但如果你有一些非标准的机器,结果可能会有所不同(如崩溃)。

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