浮点数的字节顺序

3
我正在开发Tormenta (https://github.com/jpincas/tormenta),它支持BadgerDB (https://github.com/dgraph-io/badger)作为后端。 BadgerDB按字节顺序存储键(字节片)。我正在创建包含浮点数的键,需要按顺序存储,以便可以正确地使用Badger的键迭代。由于我的计算机科学背景不太扎实,所以我有点力不从心。

我这样编码浮点数:binary.Write(buf, binary.BigEndian, myFloat)。对于正浮点数,这很好用——键的顺序是您期望的,但是对于负浮点数,字节排序就会出问题。

顺便说一句,整数也存在同样的问题,但是通过使用b[0] ^= 1 << 7(其中b是保存编码整数结果的[]byte)翻转int的符号位,然后在检索键时翻转回来,我能够相对容易地解决它。

虽然b[0] ^= 1 << 7确实也会翻转浮点数的符号位,从而将所有负浮点数放在正浮点数之前,但负浮点数的顺序是错误(倒序)的。需要翻转符号位并反转负浮点数的顺序。

在StackOverflow上曾经有类似的问题:Sorting floating-point values using their byte-representation,并且达成了以下解决方案:

用0x8000...异或所有正数,用0xffff....异或所有负数。这应该翻转两者的符号位(使负数排在第一位),然后反转负数的排序。

然而,这超出了我的位操作技能范围,所以我希望Go位操作专家能够帮助我将其转换为一些Go代码。

1个回答

3

使用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
}

然后将 f float64 值代替为 bits 值进行序列化,就完成了。

让我们看看这个过程。我们创建一个包装类型来保存一个 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 进行 XOR,其余字节与 0x00 进行 XOR,这基本上不会对它们进行任何操作。

如果数字是负数,则将所有字节与 0xff 进行 XOR,这基本上是按位取反。

实际应用中:唯一不同的部分是序列化和 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上尝试一下。

我无法感谢你的足够 - 你真正理解了我需要的帮助水平。我需要花一些时间研究并确定哪一个是最好的。一旦我完成,我会在这里回报。再次感谢。 - jpincas
好的 - 我已经调整了你的第二个解决方案,最终得到了可行的结果!由于我只有写入的 []byte,而没有原始数字,因此我创建了这两个函数:```func flipFloat(b []byte) []byte { if b[0]>>7 > 0 { for i, bb := range b { b[i] = ^bb } } else { b[0] ^= 0x80 } return b }func flipFloatBack(b []byte) []byte { if b[0]>>7 > 0 { b[0] ^= 0x80 } else { for i, bb := range b { b[i] = ^bb } } return b }``` - jpincas

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