Sfoglia il codice sorgente

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
rdb 1 anno fa
parent
commit
8c8cbeea98

+ 158 - 0
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]);
+      }
+    }
+  }
+}

+ 1 - 0
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" */

+ 1 - 1
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);
 }
 

+ 13 - 0
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"

+ 13 - 0
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"

+ 3 - 0
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)))

+ 15 - 12
panda/src/linmath/lmatrix3_ext_src.I

@@ -39,18 +39,21 @@ __reduce__(PyObject *self) const {
  */
 INLINE_LINMATH std::string Extension<FLOATNAME(LMatrix3)>::
 __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);
 }

+ 15 - 20
panda/src/linmath/lmatrix4_ext_src.I

@@ -40,26 +40,21 @@ __reduce__(PyObject *self) const {
  */
 INLINE_LINMATH std::string Extension<FLOATNAME(LMatrix4)>::
 __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)) << ", "
-
-      << MAYBE_ZERO(_this->_m(2, 0)) << ", "
-      << MAYBE_ZERO(_this->_m(2, 1)) << ", "
-      << MAYBE_ZERO(_this->_m(2, 2)) << ", "
-      << MAYBE_ZERO(_this->_m(2, 3)) << ", "
+  for (int i = 1; i < 16; ++i) {
+    *(p++) = ',';
+    *(p++) = ' ';
+    FLOATTYPE_REPR(_this->get_data()[i], p);
+    p += strlen(p);
+  }
 
-      << 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);
 }

+ 13 - 5
panda/src/linmath/lpoint2_ext_src.I

@@ -16,11 +16,19 @@
  */
 INLINE_LINMATH std::string Extension<FLOATNAME(LPoint2)>::
 __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);
 }
 
 /**

+ 17 - 6
panda/src/linmath/lpoint3_ext_src.I

@@ -16,12 +16,23 @@
  */
 INLINE_LINMATH std::string Extension<FLOATNAME(LPoint3)>::
 __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);
 }
 
 /**

+ 21 - 7
panda/src/linmath/lpoint4_ext_src.I

@@ -16,13 +16,27 @@
  */
 INLINE_LINMATH std::string Extension<FLOATNAME(LPoint4)>::
 __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);
 }
 
 /**

+ 13 - 5
panda/src/linmath/lvecBase2_ext_src.I

@@ -29,11 +29,19 @@
  */
 INLINE_LINMATH std::string Extension<FLOATNAME(LVecBase2)>::
 __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);
 }
 
 /**

+ 18 - 6
panda/src/linmath/lvecBase3_ext_src.I

@@ -29,12 +29,23 @@
  */
 INLINE_LINMATH std::string Extension<FLOATNAME(LVecBase3)>::
 __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

+ 21 - 7
panda/src/linmath/lvecBase4_ext_src.I

@@ -29,13 +29,27 @@
  */
 INLINE_LINMATH std::string Extension<FLOATNAME(LVecBase4)>::
 __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);
 }
 
 /**

+ 13 - 5
panda/src/linmath/lvector2_ext_src.I

@@ -16,11 +16,19 @@
  */
 INLINE_LINMATH std::string Extension<FLOATNAME(LVector2)>::
 __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);
 }
 
 /**

+ 17 - 6
panda/src/linmath/lvector3_ext_src.I

@@ -16,12 +16,23 @@
  */
 INLINE_LINMATH std::string Extension<FLOATNAME(LVector3)>::
 __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);
 }
 
 /**

+ 21 - 7
panda/src/linmath/lvector4_ext_src.I

@@ -16,13 +16,27 @@
  */
 INLINE_LINMATH std::string Extension<FLOATNAME(LVector4)>::
 __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);
 }
 
 /**

+ 11 - 0
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)"

+ 13 - 0
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)"

+ 7 - 0
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)"