我正在研究由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代码。
答案 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上尝试这个。