为了账号安全,请及时绑定邮箱和手机立即绑定

浮点数的字节顺序

浮点数的字节顺序

Go
喵喔喔 2023-06-05 17:26:37
我正在研究由 BadgerDB ( https://github.com/dgraph-io/badger ) 支持的 Tormenta ( https://github.com/jpincas/tormenta )。BadgerDB 按字节顺序存储键(字节片段)。我正在创建包含需要按顺序存储的浮点数的键,以便我可以正确地使用 Badger 的键迭代。我没有扎实的 CS 背景,所以我有点力不从心。我这样编码浮点数binary.Write(buf, binary.BigEndian, myFloat):这对正浮点数很有效&mdash;&mdash;关键顺序是你所期望的,但字节顺序对负浮点数不适用。顺便说一句,int 存在同样的问题,但我能够通过翻转 int 上的符号位b[0] ^= 1 << 7(保存对bint[]byte进行编码的结果)然后在检索密钥时翻转回去来相对容易地解决这个问题。尽管b[0] ^= 1 << 7DOES 也会翻转浮点数上的符号位,从而将所有负浮点数放在正数浮点数之前,但负数浮点数的顺序是错误的(向后)。需要翻转符号位并反转负浮点数的顺序。XOR 所有正数与 0x8000... 和负数与 0xffff...。这应该翻转两者的符号位(因此负数首先出现),然后反转负数的顺序。然而,这远远超出了我的 bit-flipping-skills 水平,所以我希望 Go bit-ninja 可以帮助我将其转化为一些 Go 代码。
查看完整描述

1 回答

?
慕丝7291255

TA贡献1859条经验 获得超6个赞

使用math.Float64bits()

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

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

f := 1.0 // Some float64 value


bits := math.Float64bits(f)

if f >= 0 {

    bits ^= 0x8000000000000000

} else {

    bits ^= 0xffffffffffffffff

}

然后序列化该bits值而不是ffloat64 值,就大功告成了。


让我们看看实际效果。float64让我们创建一个包含数字及其字节的包装器类型:


type num struct {

    f    float64

    data [8]byte

}

让我们创建这些 s 的一部分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

        }

    }

}

其余的是一样的。输出也将相同。



查看完整回答
反对 回复 2023-06-05
  • 1 回答
  • 0 关注
  • 135 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信