From 8c8cbeea98abed875fbc2255e14bab2d0c7bf1b4 Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 7 Aug 2024 17:24:11 +0200 Subject: [PATCH] linmath: For repr, use as much precision as needed for roundtrip Will use as few digits as is necessary to ensure that round-tripping the same number with pstrtod will result in the same vector. This prevents eg. 3.3 formatting as 3.29999995 while still ensuring that two floats that are not equal (other than nan) are guaranteed to have a different string representation. Uses a hacky pftoa function that can be abandoned as soon as we adopt C++17, which has to_chars that does what we need Fixes #1671 --- dtool/src/dtoolbase/pdtoa.cxx | 158 ++++++++++++++++++++++++++ dtool/src/dtoolbase/pdtoa.h | 1 + dtool/src/dtoolutil/string_utils.I | 2 +- panda/src/linmath/dblnames.h | 13 +++ panda/src/linmath/fltnames.h | 13 +++ panda/src/linmath/intnames.h | 3 + panda/src/linmath/lmatrix3_ext_src.I | 27 +++-- panda/src/linmath/lmatrix4_ext_src.I | 35 +++--- panda/src/linmath/lpoint2_ext_src.I | 18 ++- panda/src/linmath/lpoint3_ext_src.I | 23 +++- panda/src/linmath/lpoint4_ext_src.I | 28 +++-- panda/src/linmath/lvecBase2_ext_src.I | 18 ++- panda/src/linmath/lvecBase3_ext_src.I | 24 +++- panda/src/linmath/lvecBase4_ext_src.I | 28 +++-- panda/src/linmath/lvector2_ext_src.I | 18 ++- panda/src/linmath/lvector3_ext_src.I | 23 +++- panda/src/linmath/lvector4_ext_src.I | 28 +++-- tests/linmath/test_lvector2.py | 11 ++ tests/linmath/test_lvector3.py | 13 +++ tests/linmath/test_lvector4.py | 7 ++ 20 files changed, 404 insertions(+), 87 deletions(-) diff --git a/dtool/src/dtoolbase/pdtoa.cxx b/dtool/src/dtoolbase/pdtoa.cxx index 2b508654ba..8055f6d49d 100644 --- a/dtool/src/dtoolbase/pdtoa.cxx +++ b/dtool/src/dtoolbase/pdtoa.cxx @@ -428,3 +428,161 @@ void pdtoa(double value, char *buffer) { Prettify(buffer, length, K); } } + +/** + * Version of pdtoa that tries hard to find the minimal string representation + * for a single-precision floating-point number. + */ +void pftoa(float value, char *buffer) { +#ifdef _MSC_VER + if (copysign(1.0f, value) < 0) { +#else + if (std::signbit(value)) { +#endif + *buffer++ = '-'; + value = -value; + } + if (cinf(value)) { + buffer[0] = 'i'; + buffer[1] = 'n'; + buffer[2] = 'f'; + buffer[3] = '\0'; + } else if (cnan(value)) { + buffer[0] = 'n'; + buffer[1] = 'a'; + buffer[2] = 'n'; + buffer[3] = '\0'; + } else if (value == 0.0f) { + buffer[0] = '0'; + buffer[1] = '.'; + buffer[2] = '0'; + buffer[3] = '\0'; + } else if (value == 1.0f) { + buffer[0] = '1'; + buffer[1] = '.'; + buffer[2] = '0'; + buffer[3] = '\0'; + } else { + int length, k; + Grisu2(value, buffer, &length, &k); + + const int kk = length + k; // 10^(kk-1) <= v < 10^kk + + if (length <= kk && kk <= 21) { + // 1234e7 -> 12340000000 + for (int i = length; i < kk; i++) + buffer[i] = '0'; + buffer[kk] = '.'; + buffer[kk + 1] = '0'; + buffer[kk + 2] = '\0'; + } + else if (0 < kk && kk <= 21) { + // 1234e-2 -> 12.34 + memmove(&buffer[kk + 1], &buffer[kk], length - kk); + + // We want the shortest possible representation, so keep reading digits + // until strtod would give the correct float value. + buffer[kk] = '\0'; + double v = (double)atoi(buffer); + buffer[kk] = '.'; + + double multiplicand = 0.1; + for (int i = kk + 1; i <= length; ++i) { + double vplus = v + (buffer[i] - '0' + 1) * multiplicand; + v += (buffer[i] - '0') * multiplicand; + multiplicand *= 0.1; + + if ((float)v == value) { + length = i; + break; + } + if (buffer[i] < '9' && (float)vplus == value) { + ++buffer[i]; + length = i; + break; + } + } + + buffer[length + 1] = '\0'; + } + else if (-6 < kk && kk <= 0) { + // 1234e-6 -> 0.001234 + const int offset = 2 - kk; + memmove(&buffer[offset], &buffer[0], length); + buffer[0] = '0'; + buffer[1] = '.'; + + // We want the shortest possible representation, so keep reading digits + // until strtod would give the correct float value. + double multiplicand = 1.0; + for (int i = 2; i < offset; i++) { + buffer[i] = '0'; + multiplicand *= 0.1; + } + if ((float)multiplicand == value) { + length = 0; + buffer[offset - 1] = '1'; + } else { + multiplicand *= 0.1; + double v = 0.0; + for (int i = offset; i < length + offset; ++i) { + double vplus = v + (buffer[i] - '0' + 1) * multiplicand; + v += (buffer[i] - '0') * multiplicand; + multiplicand *= 0.1; + + if ((float)v == value) { + buffer[i + 1] = '\0'; + break; + } + if (buffer[i] < '9' && (float)vplus == value) { + buffer[i]++; + buffer[i + 1] = '\0'; + break; + } + } + } + buffer[length + offset] = '\0'; + } + else if (length == 1) { + // 1e30 + buffer[1] = 'e'; + WriteExponent(kk - 1, &buffer[2]); + } + else { + // 1234e30 -> 1.234e33 + memmove(&buffer[2], &buffer[1], length - 1); + buffer[1] = '.'; + buffer[length + 1] = 'e'; + + double e_mult = pow(10.0, kk - 1); + if ((float)(10.0 * e_mult) == value) { + buffer[0] = '1'; + buffer[1] = 'e'; + WriteExponent(kk, &buffer[2]); + } else { + // We want the shortest possible representation, so keep reading + // digits until strtod would give the correct float value. + double v = buffer[0] - '0'; + double multiplicand = 0.1; + for (int i = 2; i < length + 2; ++i) { + double vplus = v + (buffer[i] - '0' + 1) * multiplicand; + v += (buffer[i] - '0') * multiplicand; + multiplicand *= 0.1; + + if ((float)(v * e_mult) == value) { + length = i; + buffer[i + 1] = 'e'; + break; + } + if (buffer[i] < '9' && (float)(vplus * e_mult) == value) { + buffer[i]++; + length = i; + buffer[i + 1] = 'e'; + break; + } + } + WriteExponent(kk - 1, &buffer[0 + length + 2]); + } + } + } +} diff --git a/dtool/src/dtoolbase/pdtoa.h b/dtool/src/dtoolbase/pdtoa.h index 908ca24deb..44b0d9e271 100644 --- a/dtool/src/dtoolbase/pdtoa.h +++ b/dtool/src/dtoolbase/pdtoa.h @@ -16,6 +16,7 @@ extern "C" { #endif EXPCL_DTOOL_DTOOLBASE void pdtoa(double value, char *buffer); +EXPCL_DTOOL_DTOOLBASE void pftoa(float value, char *buffer); #ifdef __cplusplus }; /* end of extern "C" */ diff --git a/dtool/src/dtoolutil/string_utils.I b/dtool/src/dtoolutil/string_utils.I index 1861e86ffa..d821deffdf 100644 --- a/dtool/src/dtoolutil/string_utils.I +++ b/dtool/src/dtoolutil/string_utils.I @@ -32,7 +32,7 @@ format_string(bool value) { INLINE std::string format_string(float value) { char buffer[32]; - pdtoa((double)value, buffer); + pftoa(value, buffer); return std::string(buffer); } diff --git a/panda/src/linmath/dblnames.h b/panda/src/linmath/dblnames.h index c2c646b750..a2e7363e3e 100644 --- a/panda/src/linmath/dblnames.h +++ b/panda/src/linmath/dblnames.h @@ -33,6 +33,7 @@ #undef FLOATTYPE_IS_INT #undef STRINGIFY #undef FLOATNAME_STR +#undef FLOATTYPE_REPR #define FLOATTYPE double #define FLOATNAME(ARG) ARG##d @@ -41,3 +42,15 @@ #define STRINGIFY(ARG) #ARG #define FLOATNAME_STR(ARG) STRINGIFY(ARG##d) + +#define FLOATTYPE_REPR(v, str) do { \ + double v_copy = (v); \ + char *into_str = (str); \ + if ((double)(long long)v_copy == v_copy) { \ + snprintf(into_str, 32, "%lld", (long long)v_copy); \ + } else { \ + pdtoa(v_copy, into_str); \ + } \ +} while (0) + +#include "pdtoa.h" diff --git a/panda/src/linmath/fltnames.h b/panda/src/linmath/fltnames.h index d8d4762ddb..a69d651f51 100644 --- a/panda/src/linmath/fltnames.h +++ b/panda/src/linmath/fltnames.h @@ -33,6 +33,7 @@ #undef FLOATTYPE_IS_INT #undef STRINGIFY #undef FLOATNAME_STR +#undef FLOATTYPE_REPR #define FLOATTYPE float #define FLOATNAME(ARG) ARG##f @@ -41,3 +42,15 @@ #define STRINGIFY(ARG) #ARG #define FLOATNAME_STR(ARG) STRINGIFY(ARG##f) + +#define FLOATTYPE_REPR(v, str) do { \ + float v_copy = (v); \ + char *into_str = (str); \ + if ((float)(int)v_copy == v_copy) { \ + snprintf(into_str, 32, "%d", (int)v_copy); \ + } else { \ + pftoa(v_copy, into_str); \ + } \ +} while (0) + +#include "pdtoa.h" diff --git a/panda/src/linmath/intnames.h b/panda/src/linmath/intnames.h index 2a01d71e05..a7e1ae7cb8 100644 --- a/panda/src/linmath/intnames.h +++ b/panda/src/linmath/intnames.h @@ -33,6 +33,7 @@ #undef FLOATTYPE_IS_INT #undef STRINGIFY #undef FLOATNAME_STR +#undef FLOATTYPE_REPR #define FLOATTYPE int #define FLOATNAME(ARG) ARG##i @@ -42,3 +43,5 @@ #define STRINGIFY(ARG) #ARG #define FLOATNAME_STR(ARG) STRINGIFY(ARG##i) + +#define FLOATTYPE_REPR(v, str) (snprintf((str), 12, "%d", (v))) diff --git a/panda/src/linmath/lmatrix3_ext_src.I b/panda/src/linmath/lmatrix3_ext_src.I index 0d92b37aa4..2d75898853 100644 --- a/panda/src/linmath/lmatrix3_ext_src.I +++ b/panda/src/linmath/lmatrix3_ext_src.I @@ -39,18 +39,21 @@ __reduce__(PyObject *self) const { */ INLINE_LINMATH std::string Extension:: __repr__() const { - std::ostringstream out; - out << "LMatrix3" << FLOATTOKEN << "(" - << MAYBE_ZERO(_this->_m(0, 0)) << ", " - << MAYBE_ZERO(_this->_m(0, 1)) << ", " - << MAYBE_ZERO(_this->_m(0, 2)) << ", " + char buf[32 * 17] = "LMatrix4"; + char *p = buf + strlen(buf); + *(p++) = FLOATTOKEN; + *(p++) = '('; + FLOATTYPE_REPR(_this->_m(0, 0), p); + p += strlen(p); - << MAYBE_ZERO(_this->_m(1, 0)) << ", " - << MAYBE_ZERO(_this->_m(1, 1)) << ", " - << MAYBE_ZERO(_this->_m(1, 2)) << ", " + for (int i = 1; i < 9; ++i) { + *(p++) = ','; + *(p++) = ' '; + FLOATTYPE_REPR(_this->get_data()[i], p); + p += strlen(p); + } - << MAYBE_ZERO(_this->_m(2, 0)) << ", " - << MAYBE_ZERO(_this->_m(2, 1)) << ", " - << MAYBE_ZERO(_this->_m(2, 2)) << ")"; - return out.str(); + *(p++) = ')'; + *p = '\0'; + return std::string(buf, p - buf); } diff --git a/panda/src/linmath/lmatrix4_ext_src.I b/panda/src/linmath/lmatrix4_ext_src.I index 68a2741bbb..a216c7f153 100644 --- a/panda/src/linmath/lmatrix4_ext_src.I +++ b/panda/src/linmath/lmatrix4_ext_src.I @@ -40,26 +40,21 @@ __reduce__(PyObject *self) const { */ INLINE_LINMATH std::string Extension:: __repr__() const { - std::ostringstream out; - out << "LMatrix4" << FLOATTOKEN << "(" - << MAYBE_ZERO(_this->_m(0, 0)) << ", " - << MAYBE_ZERO(_this->_m(0, 1)) << ", " - << MAYBE_ZERO(_this->_m(0, 2)) << ", " - << MAYBE_ZERO(_this->_m(0, 3)) << ", " + char buf[32 * 17] = "LMatrix4"; + char *p = buf + strlen(buf); + *(p++) = FLOATTOKEN; + *(p++) = '('; + FLOATTYPE_REPR(_this->_m(0, 0), p); + p += strlen(p); - << MAYBE_ZERO(_this->_m(1, 0)) << ", " - << MAYBE_ZERO(_this->_m(1, 1)) << ", " - << MAYBE_ZERO(_this->_m(1, 2)) << ", " - << MAYBE_ZERO(_this->_m(1, 3)) << ", " + for (int i = 1; i < 16; ++i) { + *(p++) = ','; + *(p++) = ' '; + FLOATTYPE_REPR(_this->get_data()[i], p); + p += strlen(p); + } - << MAYBE_ZERO(_this->_m(2, 0)) << ", " - << MAYBE_ZERO(_this->_m(2, 1)) << ", " - << MAYBE_ZERO(_this->_m(2, 2)) << ", " - << MAYBE_ZERO(_this->_m(2, 3)) << ", " - - << MAYBE_ZERO(_this->_m(3, 0)) << ", " - << MAYBE_ZERO(_this->_m(3, 1)) << ", " - << MAYBE_ZERO(_this->_m(3, 2)) << ", " - << MAYBE_ZERO(_this->_m(3, 3)) << ")"; - return out.str(); + *(p++) = ')'; + *p = '\0'; + return std::string(buf, p - buf); } diff --git a/panda/src/linmath/lpoint2_ext_src.I b/panda/src/linmath/lpoint2_ext_src.I index acfa098501..300836aa84 100644 --- a/panda/src/linmath/lpoint2_ext_src.I +++ b/panda/src/linmath/lpoint2_ext_src.I @@ -16,11 +16,19 @@ */ INLINE_LINMATH std::string Extension:: __repr__() const { - std::ostringstream out; - out << "LPoint2" << FLOATTOKEN << "(" - << MAYBE_ZERO(_this->_v(0)) << ", " - << MAYBE_ZERO(_this->_v(1)) << ")"; - return out.str(); + char buf[96] = "LPoint2"; + char *p = buf + strlen(buf); + *(p++) = FLOATTOKEN; + *(p++) = '('; + FLOATTYPE_REPR(_this->_v(0), p); + p += strlen(p); + *(p++) = ','; + *(p++) = ' '; + FLOATTYPE_REPR(_this->_v(1), p); + p += strlen(p); + *(p++) = ')'; + *p = '\0'; + return std::string(buf, p - buf); } /** diff --git a/panda/src/linmath/lpoint3_ext_src.I b/panda/src/linmath/lpoint3_ext_src.I index 95cb1dbdd3..30ebc380e9 100644 --- a/panda/src/linmath/lpoint3_ext_src.I +++ b/panda/src/linmath/lpoint3_ext_src.I @@ -16,12 +16,23 @@ */ INLINE_LINMATH std::string Extension:: __repr__() const { - std::ostringstream out; - out << "LPoint3" << FLOATTOKEN << "(" - << MAYBE_ZERO(_this->_v(0)) << ", " - << MAYBE_ZERO(_this->_v(1)) << ", " - << MAYBE_ZERO(_this->_v(2)) << ")"; - return out.str(); + char buf[128] = "LPoint3"; + char *p = buf + strlen(buf); + *(p++) = FLOATTOKEN; + *(p++) = '('; + FLOATTYPE_REPR(_this->_v(0), p); + p += strlen(p); + *(p++) = ','; + *(p++) = ' '; + FLOATTYPE_REPR(_this->_v(1), p); + p += strlen(p); + *(p++) = ','; + *(p++) = ' '; + FLOATTYPE_REPR(_this->_v(2), p); + p += strlen(p); + *(p++) = ')'; + *p = '\0'; + return std::string(buf, p - buf); } /** diff --git a/panda/src/linmath/lpoint4_ext_src.I b/panda/src/linmath/lpoint4_ext_src.I index e4101b5ed9..80075edd0b 100644 --- a/panda/src/linmath/lpoint4_ext_src.I +++ b/panda/src/linmath/lpoint4_ext_src.I @@ -16,13 +16,27 @@ */ INLINE_LINMATH std::string Extension:: __repr__() const { - std::ostringstream out; - out << "LPoint4" << FLOATTOKEN << "(" - << MAYBE_ZERO(_this->_v(0)) << ", " - << MAYBE_ZERO(_this->_v(1)) << ", " - << MAYBE_ZERO(_this->_v(2)) << ", " - << MAYBE_ZERO(_this->_v(3)) << ")"; - return out.str(); + char buf[160] = "LPoint4"; + char *p = buf + strlen(buf); + *(p++) = FLOATTOKEN; + *(p++) = '('; + FLOATTYPE_REPR(_this->_v(0), p); + p += strlen(p); + *(p++) = ','; + *(p++) = ' '; + FLOATTYPE_REPR(_this->_v(1), p); + p += strlen(p); + *(p++) = ','; + *(p++) = ' '; + FLOATTYPE_REPR(_this->_v(2), p); + p += strlen(p); + *(p++) = ','; + *(p++) = ' '; + FLOATTYPE_REPR(_this->_v(3), p); + p += strlen(p); + *(p++) = ')'; + *p = '\0'; + return std::string(buf, p - buf); } /** diff --git a/panda/src/linmath/lvecBase2_ext_src.I b/panda/src/linmath/lvecBase2_ext_src.I index da9f52388d..edb0a69af6 100644 --- a/panda/src/linmath/lvecBase2_ext_src.I +++ b/panda/src/linmath/lvecBase2_ext_src.I @@ -29,11 +29,19 @@ */ INLINE_LINMATH std::string Extension:: __repr__() const { - std::ostringstream out; - out << "LVecBase2" << FLOATTOKEN << "(" - << MAYBE_ZERO(_this->_v(0)) << ", " - << MAYBE_ZERO(_this->_v(1)) << ")"; - return out.str(); + char buf[96] = "LVecBase2"; + char *p = buf + strlen(buf); + *(p++) = FLOATTOKEN; + *(p++) = '('; + FLOATTYPE_REPR(_this->_v(0), p); + p += strlen(p); + *(p++) = ','; + *(p++) = ' '; + FLOATTYPE_REPR(_this->_v(1), p); + p += strlen(p); + *(p++) = ')'; + *p = '\0'; + return std::string(buf, p - buf); } /** diff --git a/panda/src/linmath/lvecBase3_ext_src.I b/panda/src/linmath/lvecBase3_ext_src.I index e59a032b89..72913c816c 100644 --- a/panda/src/linmath/lvecBase3_ext_src.I +++ b/panda/src/linmath/lvecBase3_ext_src.I @@ -29,12 +29,23 @@ */ INLINE_LINMATH std::string Extension:: __repr__() const { - std::ostringstream out; - out << "LVecBase3" << FLOATTOKEN << "(" - << MAYBE_ZERO(_this->_v(0)) << ", " - << MAYBE_ZERO(_this->_v(1)) << ", " - << MAYBE_ZERO(_this->_v(2)) << ")"; - return out.str(); + char buf[128] = "LVecBase3"; + char *p = buf + strlen(buf); + *(p++) = FLOATTOKEN; + *(p++) = '('; + FLOATTYPE_REPR(_this->_v(0), p); + p += strlen(p); + *(p++) = ','; + *(p++) = ' '; + FLOATTYPE_REPR(_this->_v(1), p); + p += strlen(p); + *(p++) = ','; + *(p++) = ' '; + FLOATTYPE_REPR(_this->_v(2), p); + p += strlen(p); + *(p++) = ')'; + *p = '\0'; + return std::string(buf, p - buf); } /** @@ -356,3 +367,4 @@ __ceil__(PyObject *self) const { #undef PYNUMBER_FLOATTYPE #undef PY_AS_FLOATTYPE +#undef FLOATTYPE_TO_STR diff --git a/panda/src/linmath/lvecBase4_ext_src.I b/panda/src/linmath/lvecBase4_ext_src.I index 9db21d73ee..1a120e678d 100644 --- a/panda/src/linmath/lvecBase4_ext_src.I +++ b/panda/src/linmath/lvecBase4_ext_src.I @@ -29,13 +29,27 @@ */ INLINE_LINMATH std::string Extension:: __repr__() const { - std::ostringstream out; - out << "LVecBase4" << FLOATTOKEN << "(" - << MAYBE_ZERO(_this->_v(0)) << ", " - << MAYBE_ZERO(_this->_v(1)) << ", " - << MAYBE_ZERO(_this->_v(2)) << ", " - << MAYBE_ZERO(_this->_v(3)) << ")"; - return out.str(); + char buf[160] = "LVecBase4"; + char *p = buf + strlen(buf); + *(p++) = FLOATTOKEN; + *(p++) = '('; + FLOATTYPE_REPR(_this->_v(0), p); + p += strlen(p); + *(p++) = ','; + *(p++) = ' '; + FLOATTYPE_REPR(_this->_v(1), p); + p += strlen(p); + *(p++) = ','; + *(p++) = ' '; + FLOATTYPE_REPR(_this->_v(2), p); + p += strlen(p); + *(p++) = ','; + *(p++) = ' '; + FLOATTYPE_REPR(_this->_v(3), p); + p += strlen(p); + *(p++) = ')'; + *p = '\0'; + return std::string(buf, p - buf); } /** diff --git a/panda/src/linmath/lvector2_ext_src.I b/panda/src/linmath/lvector2_ext_src.I index 0d9aaa0258..2774b0c186 100644 --- a/panda/src/linmath/lvector2_ext_src.I +++ b/panda/src/linmath/lvector2_ext_src.I @@ -16,11 +16,19 @@ */ INLINE_LINMATH std::string Extension:: __repr__() const { - std::ostringstream out; - out << "LVector2" << FLOATTOKEN << "(" - << MAYBE_ZERO(_this->_v(0)) << ", " - << MAYBE_ZERO(_this->_v(1)) << ")"; - return out.str(); + char buf[96] = "LVector2"; + char *p = buf + strlen(buf); + *(p++) = FLOATTOKEN; + *(p++) = '('; + FLOATTYPE_REPR(_this->_v(0), p); + p += strlen(p); + *(p++) = ','; + *(p++) = ' '; + FLOATTYPE_REPR(_this->_v(1), p); + p += strlen(p); + *(p++) = ')'; + *p = '\0'; + return std::string(buf, p - buf); } /** diff --git a/panda/src/linmath/lvector3_ext_src.I b/panda/src/linmath/lvector3_ext_src.I index ee620ba30a..2a0413cfcb 100644 --- a/panda/src/linmath/lvector3_ext_src.I +++ b/panda/src/linmath/lvector3_ext_src.I @@ -16,12 +16,23 @@ */ INLINE_LINMATH std::string Extension:: __repr__() const { - std::ostringstream out; - out << "LVector3" << FLOATTOKEN << "(" - << MAYBE_ZERO(_this->_v(0)) << ", " - << MAYBE_ZERO(_this->_v(1)) << ", " - << MAYBE_ZERO(_this->_v(2)) << ")"; - return out.str(); + char buf[128] = "LVector3"; + char *p = buf + strlen(buf); + *(p++) = FLOATTOKEN; + *(p++) = '('; + FLOATTYPE_REPR(_this->_v(0), p); + p += strlen(p); + *(p++) = ','; + *(p++) = ' '; + FLOATTYPE_REPR(_this->_v(1), p); + p += strlen(p); + *(p++) = ','; + *(p++) = ' '; + FLOATTYPE_REPR(_this->_v(2), p); + p += strlen(p); + *(p++) = ')'; + *p = '\0'; + return std::string(buf, p - buf); } /** diff --git a/panda/src/linmath/lvector4_ext_src.I b/panda/src/linmath/lvector4_ext_src.I index 6d4f7743b6..ec5765fc95 100644 --- a/panda/src/linmath/lvector4_ext_src.I +++ b/panda/src/linmath/lvector4_ext_src.I @@ -16,13 +16,27 @@ */ INLINE_LINMATH std::string Extension:: __repr__() const { - std::ostringstream out; - out << "LVector4" << FLOATTOKEN << "(" - << MAYBE_ZERO(_this->_v(0)) << ", " - << MAYBE_ZERO(_this->_v(1)) << ", " - << MAYBE_ZERO(_this->_v(2)) << ", " - << MAYBE_ZERO(_this->_v(3)) << ")"; - return out.str(); + char buf[160] = "LVector4"; + char *p = buf + strlen(buf); + *(p++) = FLOATTOKEN; + *(p++) = '('; + FLOATTYPE_REPR(_this->_v(0), p); + p += strlen(p); + *(p++) = ','; + *(p++) = ' '; + FLOATTYPE_REPR(_this->_v(1), p); + p += strlen(p); + *(p++) = ','; + *(p++) = ' '; + FLOATTYPE_REPR(_this->_v(2), p); + p += strlen(p); + *(p++) = ','; + *(p++) = ' '; + FLOATTYPE_REPR(_this->_v(3), p); + p += strlen(p); + *(p++) = ')'; + *p = '\0'; + return std::string(buf, p - buf); } /** diff --git a/tests/linmath/test_lvector2.py b/tests/linmath/test_lvector2.py index 50d1e2e393..fddbdb5328 100644 --- a/tests/linmath/test_lvector2.py +++ b/tests/linmath/test_lvector2.py @@ -142,3 +142,14 @@ def test_vec2_floordiv(type): v = type(i) v //= -j assert v.x == i // -j + + +def test_vec2_repr(): + assert repr(Vec2F(0.1, 0.2)) == "LVector2f(0.1, 0.2)" + assert repr(Vec2F(0.3, 0.4)) == "LVector2f(0.3, 0.4)" + assert repr(Vec2F(-0.9999999403953552, 1.00000001)) == "LVector2f(-0.99999994, 1)" + assert repr(Vec2F(0.00000001, 0.0)) == "LVector2f(1e-8, 0)" + assert repr(Vec2D(0.1, 0.2)) == "LVector2d(0.1, 0.2)" + assert repr(Vec2D(0.3, 0.4)) == "LVector2d(0.3, 0.4)" + assert repr(Vec2D(-0.9999999403953552, 1.00000001)) == "LVector2d(-0.9999999403953552, 1.00000001)" + assert repr(Vec2D(0.00000001, 0.0)) == "LVector2d(1e-8, 0)" diff --git a/tests/linmath/test_lvector3.py b/tests/linmath/test_lvector3.py index 03eafaa014..62d541f854 100644 --- a/tests/linmath/test_lvector3.py +++ b/tests/linmath/test_lvector3.py @@ -127,3 +127,16 @@ def test_vec3_floordiv(type): v = type(i) v //= -j assert v.x == i // -j + + +def test_vec3_repr(): + assert repr(Vec3F(0.1, 0.2, 0.3)) == "LVector3f(0.1, 0.2, 0.3)" + assert repr(Vec3F(-0.9999999403953552, 1.00000001, 1)) == "LVector3f(-0.99999994, 1, 1)" + assert repr(Vec3F(-9.451235e29, 9.451234e-19, 1e-11)) == "LVector3f(-9.451235e29, 9.451234e-19, 1e-11)" + assert repr(Vec3F(0.001, 0.0001, 0.00001)) == "LVector3f(0.001, 0.0001, 0.00001)" + assert repr(Vec3F(-0.001, -0.0001, -0.00001)) == "LVector3f(-0.001, -0.0001, -0.00001)" + assert repr(Vec3D(0.1, 0.2, 0.3)) == "LVector3d(0.1, 0.2, 0.3)" + assert repr(Vec3D(-0.9999999403953552, 1.00000001, 1)) == "LVector3d(-0.9999999403953552, 1.00000001, 1)" + assert repr(Vec3D(-9.451235e29, 9.451234e-19, 1e-11)) == "LVector3d(-9.451235e29, 9.451234e-19, 1e-11)" + assert repr(Vec3D(0.001, 0.0001, 0.00001)) == "LVector3d(0.001, 0.0001, 0.00001)" + assert repr(Vec3D(-0.001, -0.0001, -0.00001)) == "LVector3d(-0.001, -0.0001, -0.00001)" diff --git a/tests/linmath/test_lvector4.py b/tests/linmath/test_lvector4.py index a7ead81690..2bbe440b2c 100644 --- a/tests/linmath/test_lvector4.py +++ b/tests/linmath/test_lvector4.py @@ -143,3 +143,10 @@ def test_vec4_floordiv(type): v = type(i) v //= -j assert v.x == i // -j + + +def test_vec4_repr(): + assert repr(Vec4F(0.1, 0.2, 0.3, 0.4)) == "LVector4f(0.1, 0.2, 0.3, 0.4)" + assert repr(Vec4F(-0.9999999403953552, 1.000001, 1e-8, 0.0)) == "LVector4f(-0.99999994, 1.000001, 1e-8, 0)" + assert repr(Vec4D(0.1, 0.2, 0.3, 0.4)) == "LVector4d(0.1, 0.2, 0.3, 0.4)" + assert repr(Vec4D(-0.9999999403953552, 1.00000001, 1e-8, 0.0)) == "LVector4d(-0.9999999403953552, 1.00000001, 1e-8, 0)"