Skip to content

Commit 20f392a

Browse files
authored
Merge pull request #2 from saxbophone/josh/to-string
Added Fixed.to_c_str() method
2 parents fa1702f + 60f6b36 commit 20f392a

File tree

5 files changed

+108
-0
lines changed

5 files changed

+108
-0
lines changed

sxpsxfp/include/sxpsxfp/Fixed.hpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
#else
2323
#include <sys/types.h>
2424
#endif
25+
#include <stdio.h> // snprintf
26+
#include <stdlib.h> // abs
2527

2628
/**
2729
* @todo Consider shortening the namespace name to its last component
@@ -176,6 +178,34 @@ namespace com::saxbophone::sxpsxfp {
176178
// can't use a right-shift here due to it not handling negative values properly
177179
return this->_raw_value / Fixed::SCALE;
178180
}
181+
/**
182+
* @brief Stringifies the Fixed-point value to a C-string
183+
* @param buffer pointer to array of `char`. Must be non-null and pointing to buffer of size `buffer_size`.
184+
* @param[out] buffer_size size of `buffer`. Should be at least `15`.
185+
* @returns `false` if buffer could not be written, because buffer_size wasn't big enough
186+
* @returns `true` if buffer was written
187+
* @note Although conforming C-libraries check if `buffer == nullptr` for you, the implementation of `vsnprintf()`
188+
* in libPSn00bSDK, which this project is intended for use with, doesn't seem to check if `buffer == NULL`, hence
189+
* the need to check it here.
190+
*/
191+
constexpr bool to_c_str(char* buffer, size_t buffer_size) const {
192+
// don't write to a null-pointer!
193+
if (buffer == nullptr) { return false; }
194+
// need at least 14 characters and 1 for the null-terminator
195+
if (buffer_size < 15) { return false; } // refuse if not at least this many in buffer
196+
int decimal_part = this->_raw_value / 4096; // floor-divide
197+
// decompose the fractional part into an unsigned int to allow us to scale it up for more decimal places
198+
unsigned int remainder = ((unsigned int)abs(this->_raw_value)) % 4096;
199+
// 1M is the maximum we can scale it up without overflow, since 4096*1M = 4096M, compared to max uint32 = ~4294M
200+
unsigned int fractional_part = (remainder * 1'000'000) / 4096; // this gives us 6 decimal places
201+
// can't print a negative sign if negative but decimal_part is zero
202+
if (this->_raw_value < 0 and decimal_part == 0) {
203+
snprintf(buffer, buffer_size, "-0.%06u", fractional_part);
204+
} else { // otherwise, we can rely on snprintf() to do it for us
205+
snprintf(buffer, buffer_size, "%d.%06u", decimal_part, fractional_part);
206+
}
207+
return true;
208+
}
179209
/**
180210
* @brief Prefix increment operator
181211
*/

tests/CMakeLists.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ target_sources(
1414
casting.cpp
1515
comparisons.cpp
1616
constructors.cpp
17+
conversion_to_string.cpp
18+
conversion_to_string_null.cpp
1719
division.cpp
1820
equivalences.cpp
1921
multiplication.cpp
@@ -22,6 +24,16 @@ target_sources(
2224
unary_operations.cpp
2325
user_defined_literals.cpp
2426
)
27+
# disable check for null buffer to vsnprintf() family functions for one file,
28+
# which needs to deliberately pass a null-pointer as part of the test case
29+
check_cxx_compiler_flag("-Wno-format-truncation" flag_supported)
30+
if(flag_supported)
31+
set_source_files_properties(
32+
conversion_to_string_null.cpp
33+
PROPERTIES COMPILE_FLAGS -Wno-format-truncation
34+
)
35+
endif()
36+
unset(flag_supported CACHE)
2537
target_link_libraries(
2638
tests
2739
PRIVATE

tests/config.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <cstddef>
55

66
namespace tests_config {
7+
// constexpr std::size_t ITERATIONS = 2'500'000'000;
78
constexpr std::size_t ITERATIONS = 10'000;
89
}
910

tests/conversion_to_string.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#include <limits>
2+
3+
#include <cstdlib>
4+
5+
#include <catch2/catch.hpp>
6+
7+
#include <sxpsxfp/Fixed.hpp>
8+
9+
#include "config.hpp"
10+
11+
using namespace com::saxbophone::sxpsxfp;
12+
13+
TEST_CASE("Conversion to String") {
14+
SECTION("Fixed.to_c_str() creates a C string with the value's decimal respresentation") {
15+
double i = GENERATE(
16+
take(
17+
tests_config::ITERATIONS,
18+
random(
19+
Fixed::FRACTIONAL_MIN,
20+
Fixed::FRACTIONAL_MAX
21+
)
22+
)
23+
);
24+
Fixed var = i;
25+
char output[15] = {}; // need up to 15 characters for string, including null-terminator
26+
27+
// we require the function call to return true for successful call
28+
CHECK(var.to_c_str(output, sizeof(output)));
29+
// now, try and convert that value back to double so we can approximately compare it
30+
char* cursor = nullptr;
31+
double value = std::strtod(output, &cursor);
32+
// strtod sets cursor to output if conversion failed, so check that it isn't
33+
REQUIRE(cursor != output);
34+
35+
// finally, approximately compare this value cast to double --we want them equal to 6 decimal places
36+
REQUIRE(value == Approx((double)var).margin(0.000001));
37+
}
38+
39+
SECTION("Fixed.to_c_str() returns false when given buffer_size = zero") {
40+
Fixed var;
41+
char output = {};
42+
43+
REQUIRE_FALSE(var.to_c_str(&output, 0));
44+
}
45+
46+
SECTION("Fixed.to_c_str() returns false when 1 ≤ buffer_size < 15") {
47+
std::size_t buffer_size = GENERATE(range(1U, 14U));
48+
Fixed var;
49+
char* output = new char[buffer_size] {0};
50+
51+
REQUIRE_FALSE(var.to_c_str(output, buffer_size));
52+
}
53+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#include <catch2/catch.hpp>
2+
3+
#include <sxpsxfp/Fixed.hpp>
4+
5+
using namespace com::saxbophone::sxpsxfp;
6+
7+
TEST_CASE("Fixed.to_c_str() returns false when buffer is a null pointer") {
8+
Fixed var;
9+
10+
// should fail because of null pointer and not buffer_size, the latter of which is big enough
11+
REQUIRE_FALSE(var.to_c_str(nullptr, 15));
12+
}

0 commit comments

Comments
 (0)