Browse Source

Fixed a bug in string_fromDouble where the fraction overflowed from 99999999 to 0, so that a whole unit went missing.

David Piuva 3 months ago
parent
commit
97753c918b
2 changed files with 73 additions and 1 deletions
  1. 44 1
      Source/DFPSR/api/stringAPI.cpp
  2. 29 0
      Source/test/tests/StringTest.cpp

+ 44 - 1
Source/DFPSR/api/stringAPI.cpp

@@ -308,6 +308,42 @@ static double decimalMultipliers[MAX_DECIMALS] = {
 	1000000000000000.0,
 	10000000000000000.0
 };
+static double roundingOffsets[MAX_DECIMALS] = {
+	0.05,
+	0.005,
+	0.0005,
+	0.00005,
+	0.000005,
+	0.0000005,
+	0.00000005,
+	0.000000005,
+	0.0000000005,
+	0.00000000005,
+	0.000000000005,
+	0.0000000000005,
+	0.00000000000005,
+	0.000000000000005,
+	0.0000000000000005,
+	0.00000000000000005
+};
+static uint64_t decimalLimits[MAX_DECIMALS] = {
+	9,
+	99,
+	999,
+	9999,
+	99999,
+	999999,
+	9999999,
+	99999999,
+	999999999,
+	9999999999,
+	99999999999,
+	999999999999,
+	9999999999999,
+	99999999999999,
+	999999999999999,
+	9999999999999999
+};
 void dsr::string_fromDouble(String& target, double value, int decimalCount, bool removeTrailingZeroes, DsrChar decimalCharacter, DsrChar negationCharacter) {
 	if (decimalCount < 1) decimalCount = 1;
 	if (decimalCount > MAX_DECIMALS) decimalCount = MAX_DECIMALS;
@@ -317,14 +353,21 @@ void dsr::string_fromDouble(String& target, double value, int decimalCount, bool
 		string_appendChar(target, negationCharacter);
 		remainder = -remainder;
 	}
+	// Apply an offset to make the following truncation round to the closest printable decimal.
+	int offsetIndex = decimalCount - 1;
+	remainder += roundingOffsets[offsetIndex];
 	// Get whole part
 	uint64_t whole = (uint64_t)remainder;
 	string_fromUnsigned(target, whole);
+	// Remove the whole part from the remainder.
 	remainder = remainder - whole;
 	// Print the decimal
 	string_appendChar(target, decimalCharacter);
 	// Get decimals
-	uint64_t scaledDecimals = (uint64_t)((remainder * decimalMultipliers[decimalCount - 1]) + 0.5f);
+	uint64_t scaledDecimals = uint64_t(remainder * decimalMultipliers[offsetIndex]);
+	// Limit decimals to all nines prevent losing a whole unit from fraction overflow.
+	uint64_t limit = decimalLimits[offsetIndex];
+	if (scaledDecimals > limit) scaledDecimals = limit;
 	DsrChar digits[MAX_DECIMALS]; // Using 0 to decimalCount - 1
 	int writeIndex = decimalCount - 1;
 	for (int d = 0; d < decimalCount; d++) {

+ 29 - 0
Source/test/tests/StringTest.cpp

@@ -260,6 +260,35 @@ START_TEST(String)
 	ASSERT_EQUAL(dsr::string_combine(-0.5), U"-0.5");
 	ASSERT_EQUAL(dsr::string_combine(789.123456), U"789.123456");
 	ASSERT_EQUAL(dsr::string_combine(-789.123456), U"-789.123456");
+	// Manual number serialization
+	String serializedNumber;
+	// Check that epsilon does not overflow the fraction
+	serializedNumber = U""; dsr::string_fromDouble(serializedNumber, 123.0                        ); ASSERT_EQUAL(serializedNumber, U"123.0");
+	serializedNumber = U""; dsr::string_fromDouble(serializedNumber, 123.0 + 0.0000000000000000001); ASSERT_EQUAL(serializedNumber, U"123.0");
+	serializedNumber = U""; dsr::string_fromDouble(serializedNumber, 123.0 - 0.0000000000000000001); ASSERT_EQUAL(serializedNumber, U"123.0");
+	serializedNumber = U""; dsr::string_fromDouble(serializedNumber, 734.0 + 0.0000000000000000001); ASSERT_EQUAL(serializedNumber, U"734.0");
+	serializedNumber = U""; dsr::string_fromDouble(serializedNumber, 734.0 - 0.0000000000000000001); ASSERT_EQUAL(serializedNumber, U"734.0");
+	serializedNumber = U""; dsr::string_fromDouble(serializedNumber, -15.0 + 0.0000000000000000001); ASSERT_EQUAL(serializedNumber, U"-15.0");
+	serializedNumber = U""; dsr::string_fromDouble(serializedNumber, -15.0 - 0.0000000000000000001); ASSERT_EQUAL(serializedNumber, U"-15.0");
+	// Test different settings
+	serializedNumber = U""; dsr::string_fromDouble(serializedNumber, 123.456789, -12); ASSERT_EQUAL(serializedNumber, U"123.5"); // At least one decimal
+	serializedNumber = U""; dsr::string_fromDouble(serializedNumber, 123.456789,  -1); ASSERT_EQUAL(serializedNumber, U"123.5"); // At least one decimal
+	serializedNumber = U""; dsr::string_fromDouble(serializedNumber, 123.456789,   0); ASSERT_EQUAL(serializedNumber, U"123.5"); // At least one decimal
+	serializedNumber = U""; dsr::string_fromDouble(serializedNumber, 123.456789,   1); ASSERT_EQUAL(serializedNumber, U"123.5"); // Good input
+	serializedNumber = U""; dsr::string_fromDouble(serializedNumber, 123.456789,   2); ASSERT_EQUAL(serializedNumber, U"123.46"); // Rounded
+	serializedNumber = U""; dsr::string_fromDouble(serializedNumber, 123.456789,   3); ASSERT_EQUAL(serializedNumber, U"123.457"); // Rounded
+	serializedNumber = U""; dsr::string_fromDouble(serializedNumber, 123.456789,   4); ASSERT_EQUAL(serializedNumber, U"123.4568"); // Rounded
+	serializedNumber = U""; dsr::string_fromDouble(serializedNumber, 123.456789,   5); ASSERT_EQUAL(serializedNumber, U"123.45679"); // Rounded
+	serializedNumber = U""; dsr::string_fromDouble(serializedNumber, 123.456789,   6); ASSERT_EQUAL(serializedNumber, U"123.456789"); // All decimals included, so no need to round.
+	serializedNumber = U""; dsr::string_fromDouble(serializedNumber, -123.456789, -12); ASSERT_EQUAL(serializedNumber, U"-123.5"); // At least one decimal
+	serializedNumber = U""; dsr::string_fromDouble(serializedNumber, -123.456789,  -1); ASSERT_EQUAL(serializedNumber, U"-123.5"); // At least one decimal
+	serializedNumber = U""; dsr::string_fromDouble(serializedNumber, -123.456789,   0); ASSERT_EQUAL(serializedNumber, U"-123.5"); // At least one decimal
+	serializedNumber = U""; dsr::string_fromDouble(serializedNumber, -123.456789,   1); ASSERT_EQUAL(serializedNumber, U"-123.5"); // Good input
+	serializedNumber = U""; dsr::string_fromDouble(serializedNumber, -123.456789,   2); ASSERT_EQUAL(serializedNumber, U"-123.46"); // Rounded
+	serializedNumber = U""; dsr::string_fromDouble(serializedNumber, -123.456789,   3); ASSERT_EQUAL(serializedNumber, U"-123.457"); // Rounded
+	serializedNumber = U""; dsr::string_fromDouble(serializedNumber, -123.456789,   4); ASSERT_EQUAL(serializedNumber, U"-123.4568"); // Rounded
+	serializedNumber = U""; dsr::string_fromDouble(serializedNumber, -123.456789,   5); ASSERT_EQUAL(serializedNumber, U"-123.45679"); // Rounded
+	serializedNumber = U""; dsr::string_fromDouble(serializedNumber, -123.456789,   6); ASSERT_EQUAL(serializedNumber, U"-123.456789"); // All decimals included, so no need to round.
 	// Number parsing
 	ASSERT_EQUAL(string_toInteger(U"0"), 0);
 	ASSERT_EQUAL(string_toInteger(U"-0"), 0);