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