惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The Last Watchdog
The Last Watchdog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Secure Thoughts
MongoDB | Blog
MongoDB | Blog
博客园 - Franky
T
Tor Project blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Google DeepMind News
Google DeepMind News
L
LINUX DO - 最新话题
博客园_首页
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Vercel News
Vercel News
Last Week in AI
Last Week in AI
月光博客
月光博客
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
P
Proofpoint News Feed
博客园 - 叶小钗
NISL@THU
NISL@THU
C
Check Point Blog
K
Kaspersky official blog
N
News and Events Feed by Topic
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
A
Arctic Wolf
T
Threatpost
GbyAI
GbyAI
L
LINUX DO - 热门话题
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
P
Privacy & Cybersecurity Law Blog
N
News and Events Feed by Topic
Scott Helme
Scott Helme
P
Privacy International News Feed
The Register - Security
The Register - Security
G
GRAHAM CLULEY
Recorded Future
Recorded Future
Apple Machine Learning Research
Apple Machine Learning Research
C
Cybersecurity and Infrastructure Security Agency CISA
B
Blog
Project Zero
Project Zero
Cyberwarzone
Cyberwarzone
Webroot Blog
Webroot Blog
Microsoft Security Blog
Microsoft Security Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
D
DataBreaches.Net
J
Java Code Geeks
AWS News Blog
AWS News Blog
Help Net Security
Help Net Security
Engineering at Meta
Engineering at Meta
M
MIT News - Artificial intelligence
T
Threat Research - Cisco Blogs
Google DeepMind News
Google DeepMind News

hsfzxjy 的博客

解决 VSCode + CMake + MSVC 编译器信息乱码的问题 使用 3090 部署 1.58bit 动态量化版 DeepSeek R1 671b 如何在 VS Code DevContainer 中配置 HTTP 代理 如何在跳板机背后的服务器上使用 VS Code Remote - Containers Rust 中的隐匿概念 —— Place(位置) 美术馆 一尺之槌,日取其半,1075日而竭 老生常谈:使用 Cloudflare 自选 IP 加速站点访问 辩义 State、Nation 与 Country 将 Base64 编码的数据快速转换为 Uint8Array 折腾 NPU·第1章 —— 搭建 Level Zero 开发环境 折腾 NPU·第0章 —— Intel NPU 概述与 Level-Zero 新增域名 monad.run CSS 中为特定字符设置不同字体 Arbitary Lifetime Transmutation via Rust Unsoundness Dijkstra 算法的延伸 Manacher 回文计数算法 硬卧 Go Fact: Zero-sized Field at the Rear of a Struct Has Non-zero Size Display *big.Rat Losslessly and Smartly in Golang 代码的仪式 Building Electron From Scratch 中式亲属称谓研究之一:构建半群 Some Notes on Kotlin Coroutines Git sparse-checkout and partial clones for Mega-Repos 辩义“封建” Diving from the CUDA Error 804 into a bug of libnvidia-container Modern Cryptography, GPG and Integration with Git(hub) Move the Root Partition of Ubuntu A New Programmer Kicks a Roadblock Git-based Dependencies in Dart and Go Reversy Naming 人类一败涂地 Invalid Golang Pointers Can Bite You Even If You Don't Dereference Side Project(副业) A Flaw of Promoting Complex Trait Bounds in Rust Initialize Process Pool Worker with Individual Value Rust - Python FFI From Scratch [Extending Hexo For My Site] Part 1 [Extending Hexo For My Site] Part 0 Debug a 'torch.tensor(1).cuda()' hanging 不自由的互联网 Retrieve Contents over HTTP without curl or wget [Unravelling mocona] Part 1 - Verbosity or Anti-Pattern [Unravelling mocona] Part 0 - Preface Understanding pickle in Python Rough Notes on Deploying Vaultwarden & NextCloud Bookmarks 语言狂热者与实用主义者 Demystify the randomness in CUDA kernels Performant Bulk Mutations in IndexedDB Auto Rebuild .pyx Files with pyximport Cython and Threads Obtain a Random Available TCP Port with Bash Information Theory: KL Divergence Information Theory: Entropy and Mutual Information 铁板烧 西郊线 Proof of the Gumbel Max Trick Option::as_ref Rc, RefCell and Interior Mutability Visualizing Correlation 三月十日杂感 三月一日杂感 二月十一日杂感 一月二十六日杂感 SS Configuration 一月七日杂感 四月·病 Haskell 笔记:State Monad Haskell 笔记:Monad 引论 Haskell 笔记:Applicative Haskell 笔记:Category Theory and Functor Haskell 笔记:data, type, newtype Haskell 笔记:folds 使用 Aria2 在 Ubuntu 中下载百度云资源 从伪并行的 Python 多线程说起 一个 Reentrant Error 引发的对 Python 信号机制的探索和思考 Linux 文件权限 HSFZMUN 4.0 部署小记 午后雨·科大 最后的雨夜·广州 揭秘·变态的平方根倒数算法 神坑·Python 装饰类无限递归 Python“黑魔法”之 Encoding & Decoding Ubuntu 重新映射键盘布局 为什么我要翻墙 Python“黑魔法”之 Generator Coroutines 数学美 之 判断线段相交的最简方法 除夕杂感 17 行代码实现的简易 Javascript 字符串模板 Python“黑魔法”之 Meta Classes 诗集 生活,需要被“发现” 家书·十八岁成人礼 炫技?还是需求? 【译】响应式图片的现状 【译】“为什么有这么多的编程语言?” Wisecity 商赛总结——也谈前端自动化测试 记一次 DoS 诈骗网站的经历 那一年,我们望向星空
Cohesive Digests for Ints and Floats
2025-01-14 · via hsfzxjy 的博客

On developing sdxtra there comes a problem that is to calculate cohesive digests for numeric values, in a way agnostic to their types. This post discusses the problem and presents solutions in Go, though the ideas are applicable to other languages as well.

The Cohesive Digesting Problem

Numeric Values and Their Representations

In most programming languages, numeric values are usually represented as various types, such as float64, int32, uint64, etc., to meet different requirements of precision, range, and memory usage. Internally, these types diverse in bit lengths, layouts or signedness. Therefore, even referring to the same number in math, they may have different bit representation.

package main

import (
"fmt"
"math"
)

func main() {
fmt.Printf("%016x %x\n", uint64(42), math.Float64bits(42.0))

}

Digesting

Digesting is a process that maps arbitary data into a fixed-length string, called a digest.

Digests are deterministic and discriminative. By deterministic, it means the same input data would always generate the same digest string. By discriminative, it means different input data would hopefully generate different digest strings. Though there will always be collisions, due to pigeonhole principle, modern digesting algorithms like SHA-256 are designed to minimize such probability, which effectively enables usage of digests in scenarios like data deduplication, data indexing, etc.

Modern digesting algorithms are byte-oriented. During the digesting process, one should first convert the input data into a byte sequence following a designated scheme, with which the algorithms like SHA-256 further calculate the digest string. The scheme one adopts should be carefully chosen, such that the resulting byte sequence faithfully reflects the information of the input data.

package main

import (
"crypto/sha256"
"fmt"
"encoding/binary"
)


type Foo struct {
A uint32
B string
}

func main() {
foo := Foo{42, "hello"}
h := sha256.New()
h.Write(binary.BigEndian.AppendUint32(nil, foo.A))
h.Write([]byte(foo.B))
fmt.Printf("%x\n", h.Sum(nil))
}

Above is a simple example of digesting a struct Foo using SHA-256 algorithm. The scheme here for byte-sequence generation is straightforward: we first encode the uint32 value foo.A with a 4-byte big-endian format, then append all bytes of string value foo.B to the end and finalize the byte sequence.

Type Leakage in Digesting

One might easily derive such a digesting scheme, which, however, could sometimes encode more information than what we expect.

The problem lies in the type information leakage. For the field foo.A, we not only encode the numeric value 42, the way we encode it also reveals the type information of the field – the 4-byte length, the big-endian format, altogether constitutes the byte sequence. If later we switch to int64 or float32, but keep encoding by their bit representation, the digest would change accordingly.

Well… this might frustrate our users, as most of them are unaware of the distinction between ints and floats. Imagine they enquire our application with the same set of parameters, only to find out resources exist yesterday are gone now, owing to an upgrade at midnight that modifies a field type. What a nightmare.

The Problem

Thereby we have the cohesive digesting problem: how to properly calculate digests for numeric values, such that their types are not leaked into the digested results?

As said, the problem is equivalent to finding an type-agnostic injective that converts numeric values to byte sequences. To simplify the problem, this post focuses on handling uint64 and float64 types, as they are the most generic numeric types in Go. Towards this goal, there are two possible solutions:

  1. Convert uint64 and float64 to some “super type” X, then convert X to byte sequence.
  2. Develop respective dedicated schemes for uint64 and float64.

Attempt 1: Find “Super Type” X

The first idea is natural – we purge the type information by introducing a “super type” X. This type should be able to cover all possible values of uint64 and float64, and should be able to convert between them without loss of information.

Considering integers are a subset of real nunbers, we are going to investigate two candidates: float64 and big.Float.

float64 as Super Type

One might easily think of float64 as the super type, as it has a wide value range of $-10^{308} \sim 10^{308}$, enough to cover all possible uint64 values, likely.

This is however not true. In fact, the distribution of float64s becomes “sparse” when the value is large, due to limited precision. Only integers $\leq 2^{53}$ can be represented exactly in float64, while others larger are rounded to the nearest representable value. The following code snippet demonstrates that large integers are not well preserved when coerced into float64:

package main

func main() {
println(float64(1<<53) == float64(1<<53+1))
}

Obviously, float64 must be crossed out from the list.

big.Float as Super Type

If float64 is not suitable, how about types with more precision?

big.Float is a handy type in Go that emulates arbitary precision floating-point numbers. One can tweak the precision with (big.Float).SetPrec to cover all possible uint64 and float64 values, then convert the value to a byte sequence.

func WriteUint64(v uint64, h hash.Hash) {
var f big.Float
f.SetPrec(128)
f.SetUint64(v)
b, _ := f.GobEncode()
h.Write(b)
}

func WriteFloat64(v float64, h hash.Hash) {
var f big.Float
f.SetPrec(128)
f.SetFloat64(v)
b, _ := f.GobEncode()
h.Write(b)
}

Here we set the precision of big.Float to 128 bits, such that conversion from either uint64 or float64 to big.Float won’t be truncated. We then serialize the big.Float value using GobEncode method, which converts the value to a byte sequence.

This solution is neat, yet with several drawbacks:

  • GobEncode is designed for serialization, not digesting, with no guarantee for an injection between the values and the byte sequences (though it does for now).
  • It depends on the specific implementation of GobEncode, which might change in the future.
  • It cannot handle Nan values. SetFloat64 would panic if the input is Nan. If that is a must, the solution is not applicable.
  • It is hard to replicate this solution in other languages. As math/big is unique to Go, other parts of the system written by different languages will struggle to align with this specific behavior.

Of course, one could roll their own digesting scheme based on big.Float. I instead chose to develop a dedicated scheme for uint64 and float64, as described in the next section.

Attempt 2 (Final): Develop Dedicated Schemes

To avoid the drawbacks of the first attempt, I have developed a customized scheme that cohesively maps number x to a 17-byte length sequence b. The scheme is designed as follows:

  • b[0] is the category flag. b[0] could be 0 (zero), 1 (positive), 2 (negative), 3 (positive inf), 4 (negative inf), 5 (Nan), indicating the characteristic of the digested number.
  • If x is zero or not finite (b[0] is 0, 3, 4, 5),
    • let b[1:17] be all zeros;
  • If x is non-zero finite and abs(x) < 2^64,
    • let b[1:9] be the little-endian representation of floor(abs(x)),
    • let b[9:17] be the IEEE-754 representation of frac(abs(x));
  • If x is non-zero finite and abs(x) >= 2^64,
    • b[1:9] being the IEEE-754 representation of abs(x).
    • let b[9:17] be all zeros.

A reference implementation in Go is as follows (adapted from sdxtra/…/helper.go):

const (
zero = 0
positive = 1
negative = 2
inf = 3
neginf = 4
nan = 5
)

func WriteFloat64(x float64, h hash.Hash) {
buf := make([]byte, 17)
switch {
case x == 0:
buf[0] = zero
case math.IsInf(x, 1):
buf[0] = inf
case math.IsInf(x, -1):
buf[0] = neginf
case math.IsNaN(x):
buf[0] = nan
default:
goto NORMAL
}
h.Write(buf)
return
NORMAL:
if x < 0 {
buf[0] = negative
x = -x
} else {
buf[0] = positive
}
i, f := math.Modf(x)
if i < 1<<64 {
binary.LittleEndian.PutUint64(buf[1:], uint64(i))
} else {
binary.LittleEndian.PutUint64(buf[1:], math.Float64bits(x))
}
binary.LittleEndian.PutUint64(buf[9:], math.Float64bits(f))
h.Write(buf)
}

func WriteUint64(x uint64, h hash.Hash) {
buf := new([]byte, 17)
if x > 0 {
buf[0] = positive
binary.LittleEndian.PutUint64(buf[1:], x)
}
h.Write(buf)
}

Discussion on the Scheme and Implementation

There are several points worth discussing about the final scheme and within the implementation:

Category Flag

This scheme use the first byte to signify special values, which helps handle some quirky cases of IEEE-754 floating-point numbers, including:

  1. Zeros IEEE-754 has both positive and negative zeros, which are different in bit representation but should be treated as the same value.
  2. Nans IEEE-754 has multiple representations for Nan, all of which should be treated as the same value during digesting.

Short-path for Uint64

The scheme enables direct conversion from uint64 to byte sequence, without the need to convert to float64 first. This would save some computation and accelerate the digesting process.

math.Modf

For some values, the scheme involves decomposition into their integral and fractional parts. This operation has several advantages:

  1. It forms an injection, that is, different x corresponds to different pair of (floor(x), frac(x)).
  2. It can be implemented via bit operations in most programming languages, with promising efficiency and strong consistency.

One can try to verify these properties by referring to IEEE-754 standards.


Author: hsfzxjy.
Link: .
License: CC BY-NC-ND 4.0.
All rights reserved by the author.
Commercial use of this post in any form is NOT permitted.
Non-commercial use of this post should be attributed with this block of text.