From 03eb31403d77c295d8aba0a34e93056f4f756829 Mon Sep 17 00:00:00 2001 From: Markus Ewald Date: Thu, 11 Feb 2010 21:50:41 +0000 Subject: [PATCH] Added double precision overloads for the garbage-free string builder appending methods; the string builder helper methods for floating point values now tell whether they successfully appended a value instead of using an assertion and appending a wrong(!) value; restored full test coverage for the whole assembly git-svn-id: file:///srv/devel/repo-conversion/nusu@189 d2e56fa2-650e-0410-a79f-9358c0239efd --- Source/StringBuilderHelper.Test.cs | 108 +++++++++++++++++++++++++++ Source/StringBuilderHelper.cs | 115 +++++++++++++++++++++++------ 2 files changed, 201 insertions(+), 22 deletions(-) diff --git a/Source/StringBuilderHelper.Test.cs b/Source/StringBuilderHelper.Test.cs index e2d4c35..3f63c91 100644 --- a/Source/StringBuilderHelper.Test.cs +++ b/Source/StringBuilderHelper.Test.cs @@ -89,6 +89,39 @@ namespace Nuclex.Support { Assert.AreEqual("-12345", builder.ToString()); } + /// + /// Verifies that a positive long integer is correctly appended to a string builder + /// + [Test] + public void TestAppendPositiveLong() { + StringBuilder builder = new StringBuilder(); + StringBuilderHelper.Append(builder, 12345L); + + Assert.AreEqual("12345", builder.ToString()); + } + + /// + /// Verifies that a long integer with value 0 is correctly appended to a string builder + /// + [Test] + public void TestAppendNullLong() { + StringBuilder builder = new StringBuilder(); + StringBuilderHelper.Append(builder, 0L); + + Assert.AreEqual("0", builder.ToString()); + } + + /// + /// Verifies that a negative long integer is correctly appended to a string builder + /// + [Test] + public void TestAppendNegativeLong() { + StringBuilder builder = new StringBuilder(); + StringBuilderHelper.Append(builder, -12345L); + + Assert.AreEqual("-12345", builder.ToString()); + } + /// /// Verifies that negative floating point values are correctly converted /// @@ -133,6 +166,81 @@ namespace Nuclex.Support { Assert.AreEqual("1000000000.0", builder.ToString()); } + /// + /// Verifies the behavior of the helper with unsupported floating point values + /// + [Test] + public void TestAppendOutOfRangeFloat() { + StringBuilder builder = new StringBuilder(); + Assert.IsFalse(StringBuilderHelper.Append(builder, float.PositiveInfinity)); + Assert.IsFalse(StringBuilderHelper.Append(builder, float.NegativeInfinity)); + Assert.IsFalse(StringBuilderHelper.Append(builder, float.NaN)); + Assert.IsFalse(StringBuilderHelper.Append(builder, 0.000000059604644775390625f)); + } + + /// + /// Verifies that negative double precision floating point values are + /// correctly converted + /// + [Test] + public void TestAppendNegativeDouble() { + StringBuilder builder = new StringBuilder(); + StringBuilderHelper.Append(builder, -32.015625); + + Assert.AreEqual("-32.015625", builder.ToString()); + } + + /// + /// Verifies that positive double precision floating point values are + /// correctly converted + /// + [Test] + public void TestAppendPositiveDouble() { + StringBuilder builder = new StringBuilder(); + StringBuilderHelper.Append(builder, 10.0625); + + Assert.AreEqual("10.0625", builder.ToString()); + } + + /// + /// Verifies that very small double precision floating point values are + /// correctly converted + /// + [Test] + public void TestAppendSmallDouble() { + StringBuilder builder = new StringBuilder(); + StringBuilderHelper.Append(builder, 0.00390625); + + Assert.AreEqual("0.00390625", builder.ToString()); + } + + /// + /// Verifies that very large double precision floating point values are + /// correctly converted + /// + [Test] + public void TestAppendHugeDouble() { + StringBuilder builder = new StringBuilder(); + StringBuilderHelper.Append(builder, 1000000000000000000.0); + + Assert.AreEqual("1000000000000000000.0", builder.ToString()); + } + + /// + /// Verifies the behavior of the helper with unsupported double precision + /// floating point values + /// + [Test] + public void TestAppendOutOfRangeDouble() { + StringBuilder builder = new StringBuilder(); + Assert.IsFalse(StringBuilderHelper.Append(builder, double.PositiveInfinity)); + Assert.IsFalse(StringBuilderHelper.Append(builder, double.NegativeInfinity)); + Assert.IsFalse(StringBuilderHelper.Append(builder, double.NaN)); + Assert.IsFalse( + StringBuilderHelper.Append(builder, 1.1102230246251565404236316680908e-16) + ); + } + /// /// Verifies that the contents of a string builder can be cleared /// diff --git a/Source/StringBuilderHelper.cs b/Source/StringBuilderHelper.cs index 9bac9d9..590e483 100644 --- a/Source/StringBuilderHelper.cs +++ b/Source/StringBuilderHelper.cs @@ -96,19 +96,24 @@ namespace Nuclex.Support { /// /// String builder the value will be appended to /// Value that will be appended to the string builder - public static void Append(StringBuilder builder, float value) { + /// Whether the value was inside the algorithm's supported range + /// + /// Uses an algorithm that covers the sane range of possible values but will + /// fail to render extreme values, NaNs and infinity. In these cases, false + /// is returned and the traditional double.ToString() method can be used. + /// + public static bool Append(StringBuilder builder, float value) { const int ExponentBits = 0xFF; // Bit mask for the exponent bits const int FractionalBitCount = 23; // Number of bits for fractional part const int ExponentBias = 127; // Bias subtraced from exponent const int NumericBitCount = 31; // Bits without sign - - // You do not modify these as they're calculated based on the + + // You don't need modify these as they're calculated based on the const int FractionalBits = (2 << FractionalBitCount) - 1; const int HighestFractionalBit = (1 << FractionalBitCount); const int FractionalBitCountPlusOne = FractionalBitCount + 1; int intValue = FloatHelper.ReinterpretAsInt(value); - int exponent = ((intValue >> FractionalBitCount) & ExponentBits) - ExponentBias; int mantissa = (intValue & FractionalBits) | HighestFractionalBit; @@ -116,7 +121,9 @@ namespace Nuclex.Support { int fractional; if(exponent >= 0) { if(exponent >= FractionalBitCount) { - Debug.Assert(exponent < NumericBitCount); + if(exponent >= NumericBitCount) { + return false; + } integral = mantissa << (exponent - FractionalBitCount); fractional = 0; } else { @@ -124,26 +131,13 @@ namespace Nuclex.Support { fractional = (mantissa << (exponent + 1)) & FractionalBits; } } else { - Debug.Assert(exponent >= -FractionalBitCount); + if(exponent < -FractionalBitCount) { + return false; + } integral = 0; fractional = (mantissa & FractionalBits) >> -(exponent + 1); } -/* - if(exponent >= NumericBitCount) { - throw new ArgumentException("Value too large", "value"); - } else if(exponent < -FractionalBitCount) { - throw new ArgumentException("Value too small", "value"); - } else if(exponent >= FractionalBitCount) { - integral = mantissa << (exponent - FractionalBitCount); - fractional = 0; - } else if(exponent >= 0) { - integral = mantissa >> (FractionalBitCount - exponent); - fractional = (mantissa << (exponent + 1)) & FractionalBits; - } else { // exp2 < 0 - integral = 0; - fractional = (mantissa & FractionalBits) >> -(exponent + 1); - } -*/ + // Build the integral part if(intValue < 0) { builder.Append('-'); @@ -167,6 +161,83 @@ namespace Nuclex.Support { fractional &= FractionalBits; } } + + return true; + } + + /// + /// Appends a double precision floating point value to a string builder + /// without generating garbage + /// + /// String builder the value will be appended to + /// Value that will be appended to the string builder + /// Whether the value was inside the algorithm's supported range + /// + /// Uses an algorithm that covers the sane range of possible values but will + /// fail to render extreme values, NaNs and infinity. In these cases, false + /// is returned and the traditional double.ToString() method can be used. + /// + public static bool Append(StringBuilder builder, double value) { + const long ExponentBits = 0x7FF; // Bit mask for the exponent bits + const int FractionalBitCount = 52; // Number of bits for fractional part + const int ExponentBias = 1023; // Bias subtraced from exponent + const int NumericBitCount = 63; // Bits without sign + + // You don't need modify these as they're calculated based on the + const long FractionalBits = (2L << FractionalBitCount) - 1; + const long HighestFractionalBit = (1L << FractionalBitCount); + const int FractionalBitCountPlusOne = FractionalBitCount + 1; + + long intValue = FloatHelper.ReinterpretAsLong(value); + long exponent = ((intValue >> FractionalBitCount) & ExponentBits) - ExponentBias; + long mantissa = (intValue & FractionalBits) | HighestFractionalBit; + + long integral; + long fractional; + if(exponent >= 0) { + if(exponent >= FractionalBitCount) { + if(exponent >= NumericBitCount) { + return false; + } + integral = mantissa << (int)(exponent - FractionalBitCount); + fractional = 0; + } else { + integral = mantissa >> (int)(FractionalBitCount - exponent); + fractional = (mantissa << (int)(exponent + 1)) & FractionalBits; + } + } else { + if(exponent < -FractionalBitCount) { + return false; + } + integral = 0; + fractional = (mantissa & FractionalBits) >> -(int)(exponent + 1); + } + + // Build the integral part + if(intValue < 0) { + builder.Append('-'); + } + if(integral == 0) { + builder.Append('0'); + } else { + recursiveAppend(builder, integral); + } + + builder.Append('.'); + + // Build the fractional part + if(fractional == 0) { + builder.Append('0'); + } else { + while(fractional != 0) { + fractional *= 10; + long digit = (fractional >> FractionalBitCountPlusOne); + builder.Append(numbers[digit]); + fractional &= FractionalBits; + } + } + + return true; } /// Recursively appends a number's characters to a string builder