浮点数的字节顺序
Byte ordering of floats
我正在研究 Tormenta (https://github.com/jpincas/tormenta) which is backed by BadgerDB (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
确实也翻转浮点数的符号位,因此将所有负数浮点数放在正数浮点数之前,但负数浮点数的顺序不正确(向后)。需要翻转符号位,反转负数浮点数的顺序。
有人在 Whosebug 上提出了类似的问题:,解决方案被同意为:
XOR all positive numbers with 0x8000... and negative numbers with 0xffff.... This should flip the sign bit on both (so negative numbers go first), and then reverse the ordering on negative numbers.
然而,这远远超出了我的 bit-flipping-skills 水平,所以我希望 Go bit-ninja 可以帮助我将其转化为一些 Go 代码。
使用math.Float64bits()
您可以使用 math.Float64bits()
,其中 returns 一个 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
值,然后对字节执行异或运算。
如果数字是正数(或零),将第一个字节与 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.
上试试这个
我正在研究 Tormenta (https://github.com/jpincas/tormenta) which is backed by BadgerDB (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
确实也翻转浮点数的符号位,因此将所有负数浮点数放在正数浮点数之前,但负数浮点数的顺序不正确(向后)。需要翻转符号位,反转负数浮点数的顺序。
有人在 Whosebug 上提出了类似的问题:
XOR all positive numbers with 0x8000... and negative numbers with 0xffff.... This should flip the sign bit on both (so negative numbers go first), and then reverse the ordering on negative numbers.
然而,这远远超出了我的 bit-flipping-skills 水平,所以我希望 Go bit-ninja 可以帮助我将其转化为一些 Go 代码。
使用math.Float64bits()
您可以使用 math.Float64bits()
,其中 returns 一个 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
值,然后对字节执行异或运算。
如果数字是正数(或零),将第一个字节与 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.
上试试这个