VLink  2.1.0
A high-performance communication middleware
quantize.h
浏览该文件的文档.
1 /*
2  * Copyright (C) 2026 by Thun Lu. All rights reserved.
3  * Author: Thun Lu <thun.lu@zohomail.cn>
4  * Repo: https://github.com/thun-res/vlink
5  * _ __ __ _ __
6  * | | / / / / (_) ____ / /__
7  * | | / / / / / / / __ \ / //_/
8  * | |/ / / /___ / / / / / / / ,<
9  * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23 
24 /**
25  * @file quantize.h
26  * @brief Header-only helpers for linear integer quantisation and dequantisation.
27  *
28  * @details
29  * @c vlink::Quantize provides the small, deterministic numeric conversion used by compact
30  * containers such as @c zerocopy::PointCloud. It linearly maps a caller supplied real interval
31  * (@c quant_min, @c quant_max) to an integral storage interval and provides the inverse mapping
32  * for reads. The helpers are templates so the public API stays allocation-free, exception-free
33  * and independent of any particular point-cloud type.
34  *
35  * @par Mapping model
36  *
37  * | Step | Behaviour |
38  * | ---------- | ------------------------------------------------------------------------- |
39  * | Encode | Map @c value from @c [quant_min,quant_max] into @c QuantT storage. |
40  * | Decode | Map an integral storage value back with the same linear transform. |
41  * | Rounding | Round half away from zero before casting to the storage type. |
42  * | Type math | Use @c std::common_type_t<float,...> so all-integer inputs still divide in floating point. |
43  * | Bad range | Return zero when any input is NaN or @c quant_max <= @c quant_min. |
44  * | Saturation | Clamp encoded values to the storage limits when the scaled value overflows. |
45  *
46  * For signed storage types, the normal mapping interval is symmetric:
47  * @c [-std::numeric_limits<T>::max(),std::numeric_limits<T>::max()]. The most negative storage
48  * value, such as @c -32768 for @c int16_t, is reserved for negative saturation. It is accepted
49  * by @c decode, but because it sits one step below the normal interval it can decode slightly
50  * below @c quant_min. This keeps @c 0 centred for ranges like @c [-extent,+extent].
51  *
52  * @par Typical usage
53  * @code
54  * constexpr int extent = 100;
55  *
56  * int16_t qx = vlink::Quantize::encode<int16_t>(extent, x); // maps [-extent,+extent]
57  * float x_out = vlink::Quantize::decode<float>(extent, qx);
58  * @endcode
59  *
60  * @par Example
61  * @code
62  * int16_t stored = vlink::Quantize::encode<int16_t>(-10, 10, 1.25f);
63  * float value = vlink::Quantize::decode<float>(-10, 10, stored);
64  * @endcode
65  */
66 
67 #pragma once
68 
69 #include <cmath>
70 #include <limits>
71 #include <type_traits>
72 
73 #include "./macros.h"
74 
75 namespace vlink {
76 
77 /**
78  * @namespace vlink::Quantize
79  * @brief Stateless linear quantisation helpers.
80  */
81 namespace Quantize { // NOLINT(readability-identifier-naming)
82 
83 /**
84  * @brief Quantizes a value from a real range into an integral type.
85  *
86  * @param quant_min Minimum value of the real range.
87  * @param quant_max Maximum value of the real range.
88  * @param value Value to quantize.
89  * @return Quantized value, or @c 0 for an invalid / non-finite range or NaN input.
90  */
91 template <typename QuantT, typename MinT, typename MaxT, typename ValueT>
92 [[nodiscard]] QuantT encode(MinT quant_min, MaxT quant_max, ValueT value) noexcept;
93 
94 /**
95  * @brief Dequantizes an integral value into a real range.
96  *
97  * @param quant_min Minimum value of the real range.
98  * @param quant_max Maximum value of the real range.
99  * @param value Quantized value.
100  * @return Dequantized value, or @c 0 for an invalid / non-finite range.
101  */
102 template <typename ReturnT, typename MinT, typename MaxT, typename ValueT>
103 [[nodiscard]] ReturnT decode(MinT quant_min, MaxT quant_max, ValueT value) noexcept;
104 
105 /**
106  * @brief Quantizes a value from @c [-extent,+extent] into a signed integral type.
107  *
108  * @details Equivalent to @c encode<QuantT>(-extent, extent, value), but avoids
109  * the generic min/max mapping setup for symmetric extent ranges.
110  *
111  * @param extent Positive symmetric extent.
112  * @param value Value to quantize.
113  * @return Quantized value, or @c 0 for invalid / non-finite extent or NaN input.
114  */
115 template <typename QuantT, typename ExtentT, typename ValueT>
116 [[nodiscard]] QuantT encode(ExtentT extent, ValueT value) noexcept;
117 
118 /**
119  * @brief Dequantizes a signed integral value from @c [-extent,+extent].
120  *
121  * @details Equivalent to @c decode<ReturnT>(-extent, extent, value), but avoids
122  * the generic min/max mapping setup for symmetric extent ranges.
123  *
124  * @param extent Positive symmetric extent.
125  * @param value Quantized value.
126  * @return Dequantized value, or @c 0 for invalid / non-finite extent.
127  */
128 template <typename ReturnT, typename ExtentT, typename ValueT>
129 [[nodiscard]] ReturnT decode(ExtentT extent, ValueT value) noexcept;
130 
131 ////////////////////////////////////////////////////////////////
132 /// Details
133 ////////////////////////////////////////////////////////////////
134 
135 template <typename QuantT, typename MinT, typename MaxT, typename ValueT>
136 inline QuantT encode(MinT quant_min, MaxT quant_max, ValueT value) noexcept {
137  static_assert(std::is_integral_v<QuantT> && !std::is_same_v<QuantT, bool>, "QuantT must be integral.");
138  static_assert(std::is_arithmetic_v<MinT>, "MinT must be arithmetic.");
139  static_assert(std::is_arithmetic_v<MaxT>, "MaxT must be arithmetic.");
140  static_assert(std::is_arithmetic_v<ValueT>, "ValueT must be arithmetic.");
141 
142  using CalcT = std::common_type_t<float, MinT, MaxT, ValueT>;
143 
144  auto min = static_cast<CalcT>(quant_min);
145  auto max = static_cast<CalcT>(quant_max);
146  auto target = static_cast<CalcT>(value);
147 
148  if VUNLIKELY (!std::isfinite(min) || !std::isfinite(max) || std::isnan(target) || max <= min) {
149  return static_cast<QuantT>(0);
150  }
151 
152  auto quant_max_value = static_cast<CalcT>(std::numeric_limits<QuantT>::max());
153  auto quant_min_value =
154  std::is_signed_v<QuantT> ? -quant_max_value : static_cast<CalcT>(std::numeric_limits<QuantT>::lowest());
155 
156  auto quant_lowest_value = static_cast<CalcT>(std::numeric_limits<QuantT>::lowest());
157  auto quant_range = quant_max_value - quant_min_value;
158 
159  auto scaled = ((target - min) * quant_range / (max - min)) + quant_min_value;
160 
161  if VUNLIKELY (!std::isfinite(scaled)) {
162  auto scale = std::fmax(std::fabs(min), std::fabs(max));
163  auto ratio = ((target / scale) - (min / scale)) / ((max / scale) - (min / scale));
164  scaled = (ratio * quant_range) + quant_min_value;
165  }
166 
167  auto rounded = scaled >= static_cast<CalcT>(0) ? scaled + static_cast<CalcT>(0.5) : scaled - static_cast<CalcT>(0.5);
168 
169  if VUNLIKELY (std::isnan(rounded)) {
170  return static_cast<QuantT>(0);
171  }
172 
173  if VUNLIKELY (rounded >= quant_max_value) {
174  return std::numeric_limits<QuantT>::max();
175  }
176 
177  if VUNLIKELY (rounded <= quant_lowest_value) {
178  return std::numeric_limits<QuantT>::lowest();
179  }
180 
181  return static_cast<QuantT>(rounded);
182 }
183 
184 template <typename QuantT, typename ExtentT, typename ValueT>
185 inline QuantT encode(ExtentT extent, ValueT value) noexcept {
186  static_assert(std::is_integral_v<QuantT> && std::is_signed_v<QuantT> && !std::is_same_v<QuantT, bool>,
187  "QuantT must be a signed integral type.");
188  static_assert(std::is_arithmetic_v<ExtentT>, "ExtentT must be arithmetic.");
189  static_assert(std::is_arithmetic_v<ValueT>, "ValueT must be arithmetic.");
190 
191  using CalcT = std::common_type_t<float, ExtentT, ValueT>;
192 
193  auto extent_value = static_cast<CalcT>(extent);
194  auto target = static_cast<CalcT>(value);
195 
196  if VUNLIKELY (!std::isfinite(extent_value) || std::isnan(target) || extent_value <= static_cast<CalcT>(0)) {
197  return static_cast<QuantT>(0);
198  }
199 
200  auto quant_max_value = static_cast<CalcT>(std::numeric_limits<QuantT>::max());
201  auto quant_lowest_value = static_cast<CalcT>(std::numeric_limits<QuantT>::lowest());
202  auto scaled = target * quant_max_value / extent_value;
203 
204  if VUNLIKELY (!std::isfinite(scaled) && std::isfinite(target)) {
205  scaled = (target / extent_value) * quant_max_value;
206  }
207 
208  auto rounded = scaled >= static_cast<CalcT>(0) ? scaled + static_cast<CalcT>(0.5) : scaled - static_cast<CalcT>(0.5);
209 
210  if VUNLIKELY (std::isnan(rounded)) {
211  return static_cast<QuantT>(0);
212  }
213 
214  if VUNLIKELY (rounded >= quant_max_value) {
215  return std::numeric_limits<QuantT>::max();
216  }
217 
218  if VUNLIKELY (rounded <= quant_lowest_value) {
219  return std::numeric_limits<QuantT>::lowest();
220  }
221 
222  return static_cast<QuantT>(rounded);
223 }
224 
225 template <typename ReturnT, typename MinT, typename MaxT, typename ValueT>
226 inline ReturnT decode(MinT quant_min, MaxT quant_max, ValueT value) noexcept {
227  static_assert(std::is_arithmetic_v<ReturnT>, "ReturnT must be arithmetic.");
228  static_assert(std::is_arithmetic_v<MinT>, "MinT must be arithmetic.");
229  static_assert(std::is_arithmetic_v<MaxT>, "MaxT must be arithmetic.");
230  static_assert(std::is_integral_v<ValueT> && !std::is_same_v<ValueT, bool>, "ValueT must be integral.");
231 
232  using CalcT = std::common_type_t<float, ReturnT, MinT, MaxT>;
233 
234  auto min = static_cast<CalcT>(quant_min);
235  auto max = static_cast<CalcT>(quant_max);
236  auto target = static_cast<CalcT>(value);
237 
238  if VUNLIKELY (!std::isfinite(min) || !std::isfinite(max) || std::isnan(target) || max <= min) {
239  return static_cast<ReturnT>(0);
240  }
241 
242  auto quant_max_value = static_cast<CalcT>(std::numeric_limits<ValueT>::max());
243  auto quant_min_value =
244  std::is_signed_v<ValueT> ? -quant_max_value : static_cast<CalcT>(std::numeric_limits<ValueT>::lowest());
245 
246  auto quant_lowest_value = static_cast<CalcT>(std::numeric_limits<ValueT>::lowest());
247  auto quant_range = quant_max_value - quant_min_value;
248 
249  if VUNLIKELY (target >= quant_max_value) {
250  target = quant_max_value;
251  } else if VUNLIKELY (target <= quant_lowest_value) {
252  target = quant_lowest_value;
253  }
254 
255  auto decoded = ((target - quant_min_value) * (max - min) / quant_range) + min;
256 
257  if VUNLIKELY (!std::isfinite(decoded)) {
258  auto ratio = (target - quant_min_value) / quant_range;
259  auto scale = std::fmax(std::fabs(min), std::fabs(max));
260  auto normalized = ((static_cast<CalcT>(1) - ratio) * (min / scale)) + (ratio * (max / scale));
261  decoded = normalized * scale;
262  }
263 
264  if VUNLIKELY (std::isnan(decoded)) {
265  return static_cast<ReturnT>(0);
266  }
267 
268  if constexpr (std::is_integral_v<ReturnT>) {
269  auto return_max_value = static_cast<CalcT>(std::numeric_limits<ReturnT>::max());
270  auto return_lowest_value = static_cast<CalcT>(std::numeric_limits<ReturnT>::lowest());
271 
272  if VUNLIKELY (decoded >= return_max_value) {
273  return std::numeric_limits<ReturnT>::max();
274  }
275 
276  if VUNLIKELY (decoded <= return_lowest_value) {
277  return std::numeric_limits<ReturnT>::lowest();
278  }
279  }
280 
281  return static_cast<ReturnT>(decoded);
282 }
283 
284 template <typename ReturnT, typename ExtentT, typename ValueT>
285 inline ReturnT decode(ExtentT extent, ValueT value) noexcept {
286  static_assert(std::is_arithmetic_v<ReturnT>, "ReturnT must be arithmetic.");
287  static_assert(std::is_arithmetic_v<ExtentT>, "ExtentT must be arithmetic.");
288  static_assert(std::is_integral_v<ValueT> && std::is_signed_v<ValueT> && !std::is_same_v<ValueT, bool>,
289  "ValueT must be a signed integral type.");
290 
291  using CalcT = std::common_type_t<float, ReturnT, ExtentT, ValueT>;
292 
293  auto extent_value = static_cast<CalcT>(extent);
294 
295  if VUNLIKELY (!std::isfinite(extent_value) || extent_value <= static_cast<CalcT>(0)) {
296  return static_cast<ReturnT>(0);
297  }
298 
299  auto target = static_cast<CalcT>(value);
300  auto quant_max_value = static_cast<CalcT>(std::numeric_limits<ValueT>::max());
301  auto decoded = target * extent_value / quant_max_value;
302 
303  if VUNLIKELY (!std::isfinite(decoded)) {
304  decoded = (target / quant_max_value) * extent_value;
305  }
306 
307  if VUNLIKELY (std::isnan(decoded)) {
308  return static_cast<ReturnT>(0);
309  }
310 
311  if constexpr (std::is_integral_v<ReturnT>) {
312  auto return_max_value = static_cast<CalcT>(std::numeric_limits<ReturnT>::max());
313  auto return_lowest_value = static_cast<CalcT>(std::numeric_limits<ReturnT>::lowest());
314 
315  if VUNLIKELY (decoded >= return_max_value) {
316  return std::numeric_limits<ReturnT>::max();
317  }
318 
319  if VUNLIKELY (decoded <= return_lowest_value) {
320  return std::numeric_limits<ReturnT>::lowest();
321  }
322  }
323 
324  return static_cast<ReturnT>(decoded);
325 }
326 
327 } // namespace Quantize
328 
329 } // namespace vlink
Cross-platform macros for visibility, branch hints, copy prevention, singletons and string helpers.
#define VUNLIKELY(...)
Short alias for VLINK_UNLIKELY.
Definition: macros.h:289