CHERIoT RTOS
A compartmentalised RTOS for CHERIoT hardware
Loading...
Searching...
No Matches
float_to_string_helpers.hh
1#pragma once
2// Copyright Benoit Blanchon and CHERIoT Contributors.
3// SPDX-License-Identifier: MIT
4//
5// This code is derived from the ArduinoJson project:
6// Copyright Benoit Blanchon 2014-2017
7// MIT License
8
9/**
10 * This file provides routines to help convert from floating-point values to
11 * strings.
12 *
13 * This uses the algorithm described here:
14 *
15 * https://blog.benoitblanchon.fr/lightweight-float-to-string/
16 *
17 * This approach is optimised for code size, not performance or accuracy. It
18 * will give more rounding errors than common implementations but is a fraction
19 * of the code size.
20 */
21
22#include <array>
23#include <type_traits>
24
25namespace
26{
27 /**
28 * Number of powers of ten required to represent all values that fit in any
29 * supported floating-point type. 9 is sufficient for IEEE 754 64-bit
30 * floating-point values. Currently, CHERIoT does not provide a long
31 * double type.
32 */
33 constexpr size_t NumberOfPowersOfTen = 9;
34
35 /**
36 * Helper that computes the necessary value of `NumberOfPowersOfTen` for a
37 * given type.
38 */
39 template<typename T>
40 constexpr size_t NecessaryNumberOfPowersOfTen = []() {
41 return 32 - __builtin_clz(std::numeric_limits<T>::max_exponent10);
42 }();
43
44 /**
45 * Set of supported floating-point types.
46 *
47 * If new types are added to the allowed list, the
48 * `NecessaryNumberOfPowersOfTen` part of the expression must also be
49 * updated.
50 */
51 template<typename T>
52 concept SupportedFloatingPointType = requires() {
53 requires((std::is_same_v<T, float> || std::is_same_v<T, double>) &&
55 };
56
57 /**
58 * Table of powers of ten. These are expensive to compute dynamically.
59 */
60 template<typename T>
61 constexpr std::array<T, NumberOfPowersOfTen> PositiveBinaryPowersOfTen = {
62 static_cast<T>(1e1),
63 static_cast<T>(1e2),
64 static_cast<T>(1e4),
65 static_cast<T>(1e8),
66 static_cast<T>(1e16),
67 static_cast<T>(1e32),
68 static_cast<T>(1e64),
69 static_cast<T>(1e128),
70 static_cast<T>(1e256)};
71
72 /**
73 * Table of negative powers of ten. These are expensive to compute
74 * dynamically.
75 */
76 template<typename T>
77 constexpr std::array<T, NumberOfPowersOfTen> NegativeBinaryPowersOfTen = {
78 static_cast<T>(1e-1),
79 static_cast<T>(1e-2),
80 static_cast<T>(1e-4),
81 static_cast<T>(1e-8),
82 static_cast<T>(1e-16),
83 static_cast<T>(1e-32),
84 static_cast<T>(1e-64),
85 static_cast<T>(1e-128),
86 static_cast<T>(1e-256)};
87
88 /// Largest value that is printed without an exponent
89 template<typename T>
91
92 /// Smallest value that is printed without an exponent
93 template<typename T>
95
96 /**
97 * Scale the floating-point value up or down in powers of ten so that the
98 * decimal point will end up in a sensible place.
99 *
100 * The return value is the number of powers of ten (positive or negative)
101 * that the value was scaled by.
102 *
103 * This uses binary exponentiation, see the blog post linked at the top of
104 * the file for more details.
105 */
106 template<SupportedFloatingPointType T>
107 inline int normalize(T &value)
108 {
109 int powersOf10 = 0;
110
111 int8_t index = NecessaryNumberOfPowersOfTen<T> - 1;
112 int bit = 1 << index;
113
115 {
116 for (; index >= 0; index--)
117 {
118 if (value >= PositiveBinaryPowersOfTen<T>[index])
119 {
120 value *= NegativeBinaryPowersOfTen<T>[index];
121 powersOf10 = (powersOf10 + bit);
122 }
123 bit >>= 1;
124 }
125 }
126
127 if (value > 0 && value <= NegativeExponentiationThreshold<T>)
128 {
129 for (; index >= 0; index--)
130 {
131 if (value < NegativeBinaryPowersOfTen<T>[index] * 10)
132 {
133 value *= PositiveBinaryPowersOfTen<T>[index];
134 powersOf10 = (powersOf10 - bit);
135 }
136 bit >>= 1;
137 }
138 }
139
140 return powersOf10;
141 }
142
143 /**
144 * A floating-point value decomposed into components for printing.
145 */
147 {
148 /// The integral component (before the decimal point).
149 uint32_t integral;
150 /// The decimal component (after the decimal point).
151 uint32_t decimal;
152 /// The exponent (the power of ten that this is raised to).
153 int16_t exponent;
154 /**
155 * The number of decimal places in the exponent. The exponent is
156 * represented as a value that can be converted to a string with
157 * integer-to-string conversion. This may have leading zeroes. For
158 * example, the number 123.045 will have an `integral` value of 123, a
159 * `decimal` value of 45, an `exponent` of 1, and a `decimalPlaces` of
160 * 3. The value 45 must therefore be printed with a leading zero.
161 */
163 };
164
165 constexpr uint32_t pow10(int exponent)
166 {
167 return (exponent == 0) ? 1 : 10 * pow10(exponent - 1);
168 }
169
170 template<SupportedFloatingPointType T>
171 FloatParts decompose_float(T value, int8_t decimalPlaces)
172 {
173 uint32_t maxDecimalPart = pow10(decimalPlaces);
174
175 int exponent = normalize(value);
176
177 uint32_t integral = static_cast<uint32_t>(value);
178 // reduce number of decimal places by the number of integral places
179 for (uint32_t tmp = integral; tmp >= 10; tmp /= 10)
180 {
181 maxDecimalPart /= 10;
182 decimalPlaces--;
183 }
184
185 T remainder = (value - T(integral)) * T(maxDecimalPart);
186
187 uint32_t decimal = uint32_t(remainder);
188 remainder = remainder - T(decimal);
189
190 // rounding:
191 // increment by 1 if remainder >= 0.5
192 decimal += uint32_t(remainder * 2);
193 if (decimal >= maxDecimalPart)
194 {
195 decimal = 0;
196 integral++;
197 if (exponent && integral >= 10)
198 {
199 exponent++;
200 integral = 1;
201 }
202 }
203
204 // remove trailing zeros
205 while (decimal % 10 == 0 && decimalPlaces > 0)
206 {
207 decimal /= 10;
208 decimalPlaces--;
209 }
210
211 return {
212 integral, decimal, static_cast<int16_t>(exponent), decimalPlaces};
213 }
214
215} // namespace
constexpr size_t NumberOfPowersOfTen
Number of powers of ten required to represent all values that fit in any supported floating-point typ...
int normalize(T &value)
Scale the floating-point value up or down in powers of ten so that the decimal point will end up in a...
constexpr T NegativeExponentiationThreshold
Smallest value that is printed without an exponent.
constexpr size_t NecessaryNumberOfPowersOfTen
Helper that computes the necessary value of NumberOfPowersOfTen for a given type.
constexpr std::array< T, NumberOfPowersOfTen > PositiveBinaryPowersOfTen
Table of powers of ten.
constexpr std::array< T, NumberOfPowersOfTen > NegativeBinaryPowersOfTen
Table of negative powers of ten.
constexpr T PositiveExponentiationThreshold
Largest value that is printed without an exponent.
A floating-point value decomposed into components for printing.
int16_t exponent
The exponent (the power of ten that this is raised to).
uint32_t integral
The integral component (before the decimal point).
uint32_t decimal
The decimal component (after the decimal point).
int8_t decimalPlaces
The number of decimal places in the exponent.