浮点数的字节顺序

时间:2019-02-06 15:31:06

标签: go byte computer-science bit

我正在研究由BadgerDB(https://github.com/jpincas/tormenta)支持的Tormenta(https://github.com/dgraph-io/badger)。 BadgerDB以字节顺序存储键(字节片)。我正在创建包含需要按顺序存储的浮点数的键,以便可以正确使用Badger的键迭代。我没有扎实的CS背景,所以我有点不了解。

我对浮点数进行如下编码:binary.Write(buf, binary.BigEndian, myFloat)。对于正浮点数,这很好用-关键顺序是您所期望的,但是对于负浮点数,字节顺序会分解。

顺便说一句,整数也存在相同的问题,但是我能够通过使用b[0] ^= 1 << 7(其中b[]byte保留对int进行编码的结果),然后在检索密钥时向后翻转。

尽管b[0] ^= 1 << 7也会翻转浮点数的符号位,从而将所有负浮点数置于正浮点数之前,但负浮点数的排列错误(向后)。有必要翻转符号位并反转负浮点数的顺序。

在此处的Sorting floating-point values using their byte-representation上,在StackOverflow上提出了类似的问题,并且解决方案被同意为:

  

对所有正数与0x8000 ...进行异或,对负数与0xffff ....进行异或。这应该翻转两个符号上的符号位(因此负数先出现),然后对负数进行相反的顺序。

但是,这远高于我的位翻转技能水平,所以我希望Go位忍者可以帮助我将其转换为一些Go代码。

1 个答案:

答案 0 :(得分:2)

使用math.Float64bits()

您可以使用math.Float64bits()来返回一个uint64值,该值具有与传递给它的float64值相同的字节/位。

一旦有了uint64,对其执行按位操作就很简单了:

f := 1.0 // Some float64 value

bits := math.Float64bits(f)
if f >= 0 {
    bits ^= 0x8000000000000000
} else {
    bits ^= 0xffffffffffffffff
}

然后序列化bits值而不是f float64值,就完成了。

让我们看看这一点。让我们创建一个包含float64数字及其字节的包装器类型:

type num struct {
    f    float64
    data [8]byte
}

让我们创建以下num的一部分:

nums := []*num{
    {f: 1.0},
    {f: 2.0},
    {f: 0.0},
    {f: -1.0},
    {f: -2.0},
    {f: math.Pi},
}

序列化它们:

for _, n := range nums {
    bits := math.Float64bits(n.f)
    if n.f >= 0 {
        bits ^= 0x8000000000000000
    } else {
        bits ^= 0xffffffffffffffff
    }
    if err := binary.Write(bytes.NewBuffer(n.data[:0]), binary.BigEndian, bits); err != nil {
        panic(err)
    }
}

这是我们按字节对它们进行排序的方式:

sort.Slice(nums, func(i int, j int) bool {
    ni, nj := nums[i], nums[j]
    for k := range ni.data {
        if bi, bj := ni.data[k], nj.data[k]; bi < bj {
            return true // We're certain it's less
        } else if bi > bj {
            return false // We're certain it's not less
        } // We have to check the next byte
    }
    return false // If we got this far, they are equal (=> not less)
})

现在让我们看一下按字节排序后的顺序:

fmt.Println("Final order byte-wise:")
for _, n := range nums {
    fmt.Printf("% .7f %3v\n", n.f, n.data)
}

输出将是(在Go Playground上尝试):

Final order byte-wise:
-2.0000000 [ 63 255 255 255 255 255 255 255]
-1.0000000 [ 64  15 255 255 255 255 255 255]
 0.0000000 [128   0   0   0   0   0   0   0]
 1.0000000 [191 240   0   0   0   0   0   0]
 2.0000000 [192   0   0   0   0   0   0   0]
 3.1415927 [192   9  33 251  84  68  45  24]

没有math.Float64bits()

另一种选择是先序列化float64值,然后对字节执行XOR操作。

如果数字为正数(或零),则将第一个字节与0x80进行异或,然后将其余字节与0x00进行异或,这基本上与它们无关。

如果数字为负,则将所有字节与0xff进行异或,这基本上是按位取反的操作。

实际上:唯一不同的部分是序列化和XOR操作:

for _, n := range nums {
    if err := binary.Write(bytes.NewBuffer(n.data[:0]), binary.BigEndian, n.f); err != nil {
        panic(err)
    }
    if n.f >= 0 {
        n.data[0] ^= 0x80
    } else {
        for i, b := range n.data {
            n.data[i] = ^b
        }
    }
}

其余相同。输出也将相同。在Go Playground上尝试这个。