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
This commit is contained in:
parent
237fb57fc8
commit
03eb31403d
|
@ -89,6 +89,39 @@ namespace Nuclex.Support {
|
||||||
Assert.AreEqual("-12345", builder.ToString());
|
Assert.AreEqual("-12345", builder.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that a positive long integer is correctly appended to a string builder
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestAppendPositiveLong() {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
StringBuilderHelper.Append(builder, 12345L);
|
||||||
|
|
||||||
|
Assert.AreEqual("12345", builder.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that a long integer with value 0 is correctly appended to a string builder
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestAppendNullLong() {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
StringBuilderHelper.Append(builder, 0L);
|
||||||
|
|
||||||
|
Assert.AreEqual("0", builder.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that a negative long integer is correctly appended to a string builder
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestAppendNegativeLong() {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
StringBuilderHelper.Append(builder, -12345L);
|
||||||
|
|
||||||
|
Assert.AreEqual("-12345", builder.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Verifies that negative floating point values are correctly converted
|
/// Verifies that negative floating point values are correctly converted
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -133,6 +166,81 @@ namespace Nuclex.Support {
|
||||||
Assert.AreEqual("1000000000.0", builder.ToString());
|
Assert.AreEqual("1000000000.0", builder.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies the behavior of the helper with unsupported floating point values
|
||||||
|
/// </summary>
|
||||||
|
[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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that negative double precision floating point values are
|
||||||
|
/// correctly converted
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestAppendNegativeDouble() {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
StringBuilderHelper.Append(builder, -32.015625);
|
||||||
|
|
||||||
|
Assert.AreEqual("-32.015625", builder.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that positive double precision floating point values are
|
||||||
|
/// correctly converted
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestAppendPositiveDouble() {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
StringBuilderHelper.Append(builder, 10.0625);
|
||||||
|
|
||||||
|
Assert.AreEqual("10.0625", builder.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that very small double precision floating point values are
|
||||||
|
/// correctly converted
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestAppendSmallDouble() {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
StringBuilderHelper.Append(builder, 0.00390625);
|
||||||
|
|
||||||
|
Assert.AreEqual("0.00390625", builder.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that very large double precision floating point values are
|
||||||
|
/// correctly converted
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestAppendHugeDouble() {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
StringBuilderHelper.Append(builder, 1000000000000000000.0);
|
||||||
|
|
||||||
|
Assert.AreEqual("1000000000000000000.0", builder.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies the behavior of the helper with unsupported double precision
|
||||||
|
/// floating point values
|
||||||
|
/// </summary>
|
||||||
|
[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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Verifies that the contents of a string builder can be cleared
|
/// Verifies that the contents of a string builder can be cleared
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -96,19 +96,24 @@ namespace Nuclex.Support {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="builder">String builder the value will be appended to</param>
|
/// <param name="builder">String builder the value will be appended to</param>
|
||||||
/// <param name="value">Value that will be appended to the string builder</param>
|
/// <param name="value">Value that will be appended to the string builder</param>
|
||||||
public static void Append(StringBuilder builder, float value) {
|
/// <returns>Whether the value was inside the algorithm's supported range</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// 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.
|
||||||
|
/// </remarks>
|
||||||
|
public static bool Append(StringBuilder builder, float value) {
|
||||||
const int ExponentBits = 0xFF; // Bit mask for the exponent bits
|
const int ExponentBits = 0xFF; // Bit mask for the exponent bits
|
||||||
const int FractionalBitCount = 23; // Number of bits for fractional part
|
const int FractionalBitCount = 23; // Number of bits for fractional part
|
||||||
const int ExponentBias = 127; // Bias subtraced from exponent
|
const int ExponentBias = 127; // Bias subtraced from exponent
|
||||||
const int NumericBitCount = 31; // Bits without sign
|
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 FractionalBits = (2 << FractionalBitCount) - 1;
|
||||||
const int HighestFractionalBit = (1 << FractionalBitCount);
|
const int HighestFractionalBit = (1 << FractionalBitCount);
|
||||||
const int FractionalBitCountPlusOne = FractionalBitCount + 1;
|
const int FractionalBitCountPlusOne = FractionalBitCount + 1;
|
||||||
|
|
||||||
int intValue = FloatHelper.ReinterpretAsInt(value);
|
int intValue = FloatHelper.ReinterpretAsInt(value);
|
||||||
|
|
||||||
int exponent = ((intValue >> FractionalBitCount) & ExponentBits) - ExponentBias;
|
int exponent = ((intValue >> FractionalBitCount) & ExponentBits) - ExponentBias;
|
||||||
int mantissa = (intValue & FractionalBits) | HighestFractionalBit;
|
int mantissa = (intValue & FractionalBits) | HighestFractionalBit;
|
||||||
|
|
||||||
|
@ -116,7 +121,9 @@ namespace Nuclex.Support {
|
||||||
int fractional;
|
int fractional;
|
||||||
if(exponent >= 0) {
|
if(exponent >= 0) {
|
||||||
if(exponent >= FractionalBitCount) {
|
if(exponent >= FractionalBitCount) {
|
||||||
Debug.Assert(exponent < NumericBitCount);
|
if(exponent >= NumericBitCount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
integral = mantissa << (exponent - FractionalBitCount);
|
integral = mantissa << (exponent - FractionalBitCount);
|
||||||
fractional = 0;
|
fractional = 0;
|
||||||
} else {
|
} else {
|
||||||
|
@ -124,26 +131,13 @@ namespace Nuclex.Support {
|
||||||
fractional = (mantissa << (exponent + 1)) & FractionalBits;
|
fractional = (mantissa << (exponent + 1)) & FractionalBits;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Debug.Assert(exponent >= -FractionalBitCount);
|
if(exponent < -FractionalBitCount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
integral = 0;
|
integral = 0;
|
||||||
fractional = (mantissa & FractionalBits) >> -(exponent + 1);
|
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
|
// Build the integral part
|
||||||
if(intValue < 0) {
|
if(intValue < 0) {
|
||||||
builder.Append('-');
|
builder.Append('-');
|
||||||
|
@ -167,6 +161,83 @@ namespace Nuclex.Support {
|
||||||
fractional &= FractionalBits;
|
fractional &= FractionalBits;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends a double precision floating point value to a string builder
|
||||||
|
/// without generating garbage
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">String builder the value will be appended to</param>
|
||||||
|
/// <param name="value">Value that will be appended to the string builder</param>
|
||||||
|
/// <returns>Whether the value was inside the algorithm's supported range</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// 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.
|
||||||
|
/// </remarks>
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Recursively appends a number's characters to a string builder</summary>
|
/// <summary>Recursively appends a number's characters to a string builder</summary>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user