HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
float16.hpp
1// Copyright Take Vos 2020-2022.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "utility.hpp"
8#include "architecture.hpp"
9#include <cstdint>
10#include <type_traits>
11
12hi_warning_push();
13// C26472: Don't use static_cast for arithmetic conversions, Use brace initialization, gsl::narrow_cast or gsl::narrow (type.1).
14// static_cast here is used to extract bits and cause sign-extension.
15hi_warning_ignore_msvc(26472);
16
17namespace hi::inline v1 {
18
19constexpr uint32_t float16_bias = 15;
20constexpr uint32_t float32_bias = 127;
21constexpr uint32_t f32_to_f16_adjustment_exponent = float32_bias - float16_bias;
22constexpr uint32_t f32_to_f16_lowest_normal_exponent = 0x01 + f32_to_f16_adjustment_exponent;
23constexpr uint32_t f32_to_f16_infinite_exponent = 0x1f + f32_to_f16_adjustment_exponent;
24constexpr uint32_t f32_to_f16_adjustment = f32_to_f16_adjustment_exponent << 23;
25constexpr uint32_t f32_to_f16_lowest_normal = f32_to_f16_lowest_normal_exponent << 23;
26constexpr uint32_t f32_to_f16_infinite = f32_to_f16_infinite_exponent << 23;
27
28constexpr float cvtsh_ss(uint16_t value) noexcept
29{
30 // Convert the 16 bit values to 32 bit with leading zeros.
31 uint32_t u = value;
32
33 // Extract the sign bit.
34 hilet sign = (u >> 15) << 31;
35
36 // Strip the sign bit and align the exponent/mantissa boundary to a float 32.
37 u = (u << 17) >> 4;
38
39 // Adjust the bias. f32_to_f16_adjustment
40 u = u + f32_to_f16_adjustment;
41
42 // Get a mask of '1' bits when the half-float would be normal or infinite.
43 hilet is_normal = u > (f32_to_f16_lowest_normal - 1);
44
45 // Add the sign bit back in.
46 u = u | sign;
47
48 // Keep the value if normal, if denormal make it zero.
49 u = is_normal ? u : 0;
50
51 return std::bit_cast<float>(u);
52}
53
54constexpr uint16_t cvtss_sh(float value) noexcept
55{
56 // Interpret the floating point number as 32 bit-field.
57 auto u = std::bit_cast<uint32_t>(value);
58
59 // Get the sign of the floating point number as a bit mask of the upper 17 bits.
60 hilet sign = static_cast<uint32_t>(static_cast<int32_t>(u) >> 31) << 15;
61
62 // Strip sign bit.
63 u = (u << 1) >> 1;
64
65 // Get a mask of '1' bits when the half-float would be normal or infinite.
66 hilet is_normal = u > (f32_to_f16_lowest_normal - 1);
67
68 // Clamp the floating point number to where the half-float would be infinite.
69 u = std::min(u, f32_to_f16_infinite); // SSE4.1
70
71 // Convert the bias from float to half-float.
72 u = u - f32_to_f16_adjustment;
73
74 // Shift the float until it becomes a half-float. This truncates the mantissa.
75 u = u >> 13;
76
77 // Keep the value if normal, if denormal make it zero.
78 u = is_normal ? u : 0;
79
80 // Add the sign bit back in, also set the upper 16 bits so that saturated pack
81 // will work correctly when converting to int16.
82 u = u | sign;
83
84 // Saturate and pack the 32 bit integers to 16 bit integers.
85 return static_cast<uint16_t>(u);
86}
87
88struct float16 {
89 uint16_t v = 0;
90
91 constexpr float16() noexcept = default;
92 ~float16() = default;
93 constexpr float16(float16 const&) noexcept = default;
94 constexpr float16(float16&&) noexcept = default;
95 constexpr float16& operator=(float16 const&) noexcept = default;
96 constexpr float16& operator=(float16&&) noexcept = default;
97
98 constexpr explicit float16(float other) noexcept : v(cvtss_sh(other)) {}
99 constexpr explicit float16(double other) noexcept : float16(static_cast<float>(other)) {}
100 constexpr explicit float16(long double other) noexcept : float16(static_cast<float>(other)) {}
101
102 constexpr float16& operator=(float other) noexcept
103 {
104 v = cvtss_sh(other);
105 return *this;
106 }
107
108 constexpr operator float() const noexcept
109 {
110 return cvtsh_ss(v);
111 }
112
113 [[nodiscard]] static constexpr float16 from_uint16_t(uint16_t const rhs) noexcept
114 {
115 auto r = float16{};
116 r.v = rhs;
117 return r;
118 }
119
120 [[nodiscard]] constexpr uint16_t get() const noexcept
121 {
122 return v;
123 }
124
125 constexpr float16& set(uint16_t rhs) noexcept
126 {
127 v = rhs;
128 return *this;
129 }
130
131 [[nodiscard]] std::size_t hash() const noexcept
132 {
133 return std::hash<uint16_t>{}(v);
134 }
135
136 [[nodiscard]] constexpr friend bool operator==(float16 const& lhs, float16 const& rhs) noexcept
137 {
138 return static_cast<float>(lhs) == static_cast<float>(rhs);
139 }
140
141 [[nodiscard]] constexpr friend auto operator<=>(float16 const& lhs, float16 const& rhs) noexcept
142 {
143 return static_cast<float>(lhs) <=> static_cast<float>(rhs);
144 }
145
146#define HI_X_binary_math_op(op) \
147 [[nodiscard]] constexpr friend float16 operator op(float16 const& lhs, float16 const& rhs) noexcept \
148 { \
149 return float16{static_cast<float>(lhs) op static_cast<float>(rhs)}; \
150 }
151
152 // clang-format off
153 HI_X_binary_math_op(+)
154 HI_X_binary_math_op(-)
155 HI_X_binary_math_op(*)
156 HI_X_binary_math_op(/)
157 // clang-format on
158#undef HI_X_binary_math_op
159
160 //[[nodiscard]] constexpr friend float16 operator%(float16 const& lhs, float16 const& rhs) noexcept
161 //{
162 // hilet lhs_ = static_cast<float>(lhs);
163 // hilet rhs_ = static_cast<float>(rhs);
164 // hilet div_result = std::floor(lhs_ / rhs_);
165 // return float16{lhs_ - (div_result * rhs_)};
166 //}
167
168#define HI_X_binary_bit_op(op) \
169 [[nodiscard]] constexpr friend float16 operator op(float16 const& lhs, float16 const& rhs) noexcept \
170 { \
171 return float16::from_uint16_t(lhs.v op rhs.v); \
172 }
173
174 // clang-format off
175 HI_X_binary_bit_op(|)
176 HI_X_binary_bit_op(&)
177 HI_X_binary_bit_op(^)
178 // clang-format on
179#undef HI_X_binary_bit_op
180};
181
182// Check if float16 can be std::bit_cast<uint16_t>().
183static_assert(sizeof(float16) == sizeof(uint16_t));
184static_assert(std::is_trivially_copy_constructible_v<float16>);
185static_assert(std::is_trivially_move_constructible_v<float16>);
186static_assert(std::is_trivially_copy_assignable_v<float16>);
187static_assert(std::is_trivially_move_assignable_v<float16>);
188static_assert(std::is_trivially_destructible_v<float16>);
189
190static_assert(requires(float16 a) { std::bit_cast<uint16_t>(a); });
191static_assert(requires(uint16_t a) { std::bit_cast<float16>(a); });
192
193} // namespace hi::inline v1
194
195template<>
196struct std::hash<hi::float16> {
197 std::size_t operator()(hi::float16 const& rhs) noexcept
198 {
199 return rhs.hash();
200 }
201};
202
203template<typename CharT>
204struct std::formatter<hi::float16, CharT> : std::formatter<float, CharT> {
205 constexpr auto format(hi::float16 const& t, auto& fc)
206 {
207 return std::formatter<float, CharT>::format(static_cast<float>(t), fc);
208 }
209};
210
211template<>
212struct std::numeric_limits<hi::float16> {
213 using value_type = hi::float16;
214
215 static constexpr bool is_specialized = true;
216 static constexpr bool is_signed = true;
217 static constexpr bool is_integer = false;
218 static constexpr bool is_exact = false;
219 static constexpr bool has_infinity = true;
220 static constexpr bool has_quiet_NaN = true;
221 static constexpr bool has_signaling_NaN = false;
222 static constexpr float_denorm_style has_denorm = std::denorm_present;
223 static constexpr bool has_denorm_loss = false;
224 static constexpr float_round_style round_style = std::round_to_nearest;
225 static constexpr bool is_iec559 = true;
226 static constexpr bool is_bounded = true;
227 static constexpr bool is_modulo = false;
228 static constexpr int digits = 10;
229 static constexpr int digits10 = 4;
230 static constexpr int max_digits10 = 4;
231 static constexpr int min_exponent = -14;
232 static constexpr int min_exponent10 = -3;
233 static constexpr int max_exponent = 15;
234 static constexpr int max_exponent10 = 3;
235 static constexpr bool traps = false;
236 static constexpr bool tinyness_before = false;
237
238 static constexpr value_type min() noexcept
239 {
240 return hi::float16::from_uint16_t(0x0400);
241 }
242
243 static constexpr value_type lowest() noexcept
244 {
245 return hi::float16::from_uint16_t(0xfbff);
246 }
247
248 static constexpr value_type max() noexcept
249 {
250 return hi::float16::from_uint16_t(0x7bff);
251 }
252
253 static constexpr value_type epsilon() noexcept
254 {
255 return hi::float16::from_uint16_t(0xfbff);
256 }
257
258 static constexpr value_type round_error() noexcept
259 {
260 return hi::float16::from_uint16_t(0x3800); // 0.5
261 }
262
263 static constexpr value_type infinity() noexcept
264 {
265 return hi::float16::from_uint16_t(0x7c00);
266 }
267
268 static constexpr value_type quiet_NaN() noexcept
269 {
270 return hi::float16::from_uint16_t(0x7c01);
271 }
272
273 static constexpr value_type signaling_NaN() noexcept
274 {
275 return hi::float16::from_uint16_t(0x7e01);
276 }
277
278 static constexpr value_type denorm_min() noexcept
279 {
280 return hi::float16::from_uint16_t(0x0001);
281 }
282};
283
284hi_warning_pop();
Utilities used by the HikoGUI library itself.
#define hilet
Invariant should be the default for variables.
Definition utility.hpp:23
Functions and macros for handling architectural difference between compilers, CPUs and operating syst...
DOXYGEN BUG.
Definition algorithm.hpp:13
geometry/margins.hpp
Definition cache.hpp:11
Definition float16.hpp:88
T denorm_min(T... args)
T epsilon(T... args)
T infinity(T... args)
T lowest(T... args)
T max(T... args)
T min(T... args)
T operator()(T... args)
T quiet_NaN(T... args)
T round_error(T... args)
T signaling_NaN(T... args)