diff --git a/Documents/DoubleConverter.txt b/Documents/DoubleConverter.txt new file mode 100644 index 0000000..370bed8 --- /dev/null +++ b/Documents/DoubleConverter.txt @@ -0,0 +1,218 @@ +From http://www.yoda.arachsys.com/csharp/DoubleConverter.cs + + +using System; +using System.Globalization; + +/// +/// A class to allow the conversion of doubles to string representations of +/// their exact decimal values. The implementation aims for readability over +/// efficiency. +/// +public class DoubleConverter +{ + /// + /// Converts the given double to a string representation of its + /// exact decimal value. + /// + /// The double to convert. + /// A string representation of the double's exact decimal value. + public static string ToExactString (double d) + { + if (double.IsPositiveInfinity(d)) + return "+Infinity"; + if (double.IsNegativeInfinity(d)) + return "-Infinity"; + if (double.IsNaN(d)) + return "NaN"; + + // Translate the double into sign, exponent and mantissa. + long bits = BitConverter.DoubleToInt64Bits(d); + // Note that the shift is sign-extended, hence the test against -1 not 1 + bool negative = (bits < 0); + int exponent = (int) ((bits >> 52) & 0x7ffL); + long mantissa = bits & 0xfffffffffffffL; + + // Subnormal numbers; exponent is effectively one higher, + // but there's no extra normalisation bit in the mantissa + if (exponent==0) + { + exponent++; + } + // Normal numbers; leave exponent as it is but add extra + // bit to the front of the mantissa + else + { + mantissa = mantissa | (1L<<52); + } + + // Bias the exponent. It's actually biased by 1023, but we're + // treating the mantissa as m.0 rather than 0.m, so we need + // to subtract another 52 from it. + exponent -= 1075; + + if (mantissa == 0) + { + return "0"; + } + + /* Normalize */ + while((mantissa & 1) == 0) + { /* i.e., Mantissa is even */ + mantissa >>= 1; + exponent++; + } + + /// Construct a new decimal expansion with the mantissa + ArbitraryDecimal ad = new ArbitraryDecimal (mantissa); + + // If the exponent is less than 0, we need to repeatedly + // divide by 2 - which is the equivalent of multiplying + // by 5 and dividing by 10. + if (exponent < 0) + { + for (int i=0; i < -exponent; i++) + ad.MultiplyBy(5); + ad.Shift(-exponent); + } + // Otherwise, we need to repeatedly multiply by 2 + else + { + for (int i=0; i < exponent; i++) + ad.MultiplyBy(2); + } + + // Finally, return the string with an appropriate sign + if (negative) + return "-"+ad.ToString(); + else + return ad.ToString(); + } + + /// Private class used for manipulating + class ArbitraryDecimal + { + /// Digits in the decimal expansion, one byte per digit + byte[] digits; + /// + /// How many digits are *after* the decimal point + /// + int decimalPoint=0; + + /// + /// Constructs an arbitrary decimal expansion from the given long. + /// The long must not be negative. + /// + internal ArbitraryDecimal (long x) + { + string tmp = x.ToString(CultureInfo.InvariantCulture); + digits = new byte[tmp.Length]; + for (int i=0; i < tmp.Length; i++) + digits[i] = (byte) (tmp[i]-'0'); + Normalize(); + } + + /// + /// Multiplies the current expansion by the given amount, which should + /// only be 2 or 5. + /// + internal void MultiplyBy(int amount) + { + byte[] result = new byte[digits.Length+1]; + for (int i=digits.Length-1; i >= 0; i--) + { + int resultDigit = digits[i]*amount+result[i+1]; + result[i]=(byte)(resultDigit/10); + result[i+1]=(byte)(resultDigit%10); + } + if (result[0] != 0) + { + digits=result; + } + else + { + Array.Copy (result, 1, digits, 0, digits.Length); + } + Normalize(); + } + + /// + /// Shifts the decimal point; a negative value makes + /// the decimal expansion bigger (as fewer digits come after the + /// decimal place) and a positive value makes the decimal + /// expansion smaller. + /// + internal void Shift (int amount) + { + decimalPoint += amount; + } + + /// + /// Removes leading/trailing zeroes from the expansion. + /// + internal void Normalize() + { + int first; + for (first=0; first < digits.Length; first++) + if (digits[first]!=0) + break; + int last; + for (last=digits.Length-1; last >= 0; last--) + if (digits[last]!=0) + break; + + if (first==0 && last==digits.Length-1) + return; + + byte[] tmp = new byte[last-first+1]; + for (int i=0; i < tmp.Length; i++) + tmp[i]=digits[i+first]; + + decimalPoint -= digits.Length-(last+1); + digits=tmp; + } + + /// + /// Converts the value to a proper decimal string representation. + /// + public override String ToString() + { + char[] digitString = new char[digits.Length]; + for (int i=0; i < digits.Length; i++) + digitString[i] = (char)(digits[i]+'0'); + + // Simplest case - nothing after the decimal point, + // and last real digit is non-zero, eg value=35 + if (decimalPoint==0) + { + return new string (digitString); + } + + // Fairly simple case - nothing after the decimal + // point, but some 0s to add, eg value=350 + if (decimalPoint < 0) + { + return new string (digitString)+ + new string ('0', -decimalPoint); + } + + // Nothing before the decimal point, eg 0.035 + if (decimalPoint >= digitString.Length) + { + return "0."+ + new string ('0',(decimalPoint-digitString.Length))+ + new string (digitString); + } + + // Most complicated case - part of the string comes + // before the decimal point, part comes after it, + // eg 3.5 + return new string (digitString, 0, + digitString.Length-decimalPoint)+ + "."+ + new string (digitString, + digitString.Length-decimalPoint, + decimalPoint); + } + } +} diff --git a/Nuclex.Support (net-4.0).csproj b/Nuclex.Support (net-4.0).csproj index 14f0286..6981346 100644 --- a/Nuclex.Support (net-4.0).csproj +++ b/Nuclex.Support (net-4.0).csproj @@ -197,6 +197,7 @@ WeakCollection.cs + PartialStream.cs @@ -335,6 +336,7 @@ + diff --git a/Nuclex.Support (xna-4.0-phone7).csproj b/Nuclex.Support (xna-4.0-phone7).csproj index 923c4f4..2c3c325 100644 --- a/Nuclex.Support (xna-4.0-phone7).csproj +++ b/Nuclex.Support (xna-4.0-phone7).csproj @@ -228,6 +228,7 @@ WeakCollection.cs + PartialStream.cs @@ -366,6 +367,7 @@ + diff --git a/Nuclex.Support (xna-4.0-xbox360).csproj b/Nuclex.Support (xna-4.0-xbox360).csproj index 719a295..bd0dbe3 100644 --- a/Nuclex.Support (xna-4.0-xbox360).csproj +++ b/Nuclex.Support (xna-4.0-xbox360).csproj @@ -239,6 +239,7 @@ WeakCollection.cs + PartialStream.cs @@ -377,6 +378,7 @@ + diff --git a/Source/Collections/ObservableSet.Test.cs b/Source/Collections/ObservableSet.Test.cs index 7a28f76..ea9d26c 100644 --- a/Source/Collections/ObservableSet.Test.cs +++ b/Source/Collections/ObservableSet.Test.cs @@ -194,6 +194,35 @@ namespace Nuclex.Support.Collections { Assert.IsFalse(set2.IsSubsetOf(set1)); } + /// + /// Verifies that a set can determine if another set overlaps with it + /// + [Test] + public void CanDetermineOverlap() { + var set1 = new ObservableSet() { 1, 3, 5 }; + var set2 = new ObservableSet() { 3 }; + + Assert.IsTrue(set1.Overlaps(set2)); + Assert.IsTrue(set2.Overlaps(set1)); + } + + /// + /// Verifies that a set can determine if another set contains the same elements + /// + [Test] + public void CanDetermineSetEquality() { + var set1 = new ObservableSet() { 1, 3, 5 }; + var set2 = new ObservableSet() { 3, 1, 5 }; + + Assert.IsTrue(set1.SetEquals(set2)); + Assert.IsTrue(set2.SetEquals(set1)); + + set1.Add(7); + + Assert.IsFalse(set1.SetEquals(set2)); + Assert.IsFalse(set2.SetEquals(set1)); + } + /// Creates mock object for the test private MockFactory mockFactory; /// Observable set being tested diff --git a/Source/Collections/ObservableSet.cs b/Source/Collections/ObservableSet.cs index a680f15..bdb6172 100644 --- a/Source/Collections/ObservableSet.cs +++ b/Source/Collections/ObservableSet.cs @@ -37,9 +37,9 @@ namespace Nuclex.Support.Collections { ISet, ICollection, #if !NO_SPECIALIZED_COLLECTIONS - INotifyCollectionChanged, + INotifyCollectionChanged, #endif - IObservableCollection { + IObservableCollection { /// Raised when an item has been added to the collection public event EventHandler> ItemAdded; diff --git a/Source/EnumHelper.cs b/Source/EnumHelper.cs index 0ab9123..60d62c5 100644 --- a/Source/EnumHelper.cs +++ b/Source/EnumHelper.cs @@ -29,22 +29,22 @@ namespace Nuclex.Support { public static class EnumHelper { /// Returns the highest value encountered in an enumeration - /// + /// /// Enumeration of which the highest value will be returned /// /// The highest value in the enumeration - public static EnumType GetHighestValue() where EnumType : IComparable { - EnumType[] values = GetValues(); + public static TEnum GetHighestValue() where TEnum : IComparable { + TEnum[] values = GetValues(); // If the enumeration is empty, return nothing if(values.Length == 0) { - return default(EnumType); + return default(TEnum); } // Look for the highest value in the enumeration. We initialize the highest value // to the first enumeration value so we don't have to use some arbitrary starting // value which might actually appear in the enumeration. - EnumType highestValue = values[0]; + TEnum highestValue = values[0]; for(int index = 1; index < values.Length; ++index) { if(values[index].CompareTo(highestValue) > 0) { highestValue = values[index]; @@ -81,7 +81,7 @@ namespace Nuclex.Support { } /// Retrieves a list of all values contained in an enumeration - /// + /// /// Type of the enumeration whose values will be returned /// /// All values contained in the specified enumeration @@ -89,21 +89,21 @@ namespace Nuclex.Support { /// This method produces collectable garbage so it's best to only call it once /// and cache the result. /// - public static EnumType[] GetValues() { + public static TEnum[] GetValues() { #if XBOX360 || WINDOWS_PHONE - return GetValuesXbox360(); + return GetValuesXbox360(); #else - return (EnumType[])Enum.GetValues(typeof(EnumType)); + return (TEnum[])Enum.GetValues(typeof(TEnum)); #endif } /// Retrieves a list of all values contained in an enumeration - /// + /// /// Type of the enumeration whose values will be returned /// /// All values contained in the specified enumeration - internal static EnumType[] GetValuesXbox360() { - Type enumType = typeof(EnumType); + internal static TEnum[] GetValuesXbox360() { + Type enumType = typeof(TEnum); if(!enumType.IsEnum) { throw new ArgumentException( "The provided type needs to be an enumeration", "EnumType" @@ -117,9 +117,9 @@ namespace Nuclex.Support { // Create an array to hold the enumeration values and copy them over from // the fields we just retrieved - EnumType[] values = new EnumType[fieldInfos.Length]; + TEnum[] values = new TEnum[fieldInfos.Length]; for(int index = 0; index < fieldInfos.Length; ++index) { - values[index] = (EnumType)fieldInfos[index].GetValue(null); + values[index] = (TEnum)fieldInfos[index].GetValue(null); } return values; diff --git a/Source/GarbagePolicy.cs b/Source/GarbagePolicy.cs new file mode 100644 index 0000000..1d82f0e --- /dev/null +++ b/Source/GarbagePolicy.cs @@ -0,0 +1,33 @@ +#region CPL License +/* +Nuclex Framework +Copyright (C) 2002-2012 Nuclex Development Labs + +This library is free software; you can redistribute it and/or +modify it under the terms of the IBM Common Public License as +published by the IBM Corporation; either version 1.0 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +IBM Common Public License for more details. + +You should have received a copy of the IBM Common Public +License along with this library +*/ +#endregion + +using System; + +namespace Nuclex.Support { + + /// How to behave in in respect to the garbage collector + public enum GarbagePolicy { + /// Avoid feeding the garbage collector whenever possible + Avoid, + /// Accept garbage production + Accept + } + +} // namespace Nuclex.Support diff --git a/Source/PropertyChangedEventArgsHelper.cs b/Source/PropertyChangedEventArgsHelper.cs index e9105e2..bf7d3ec 100644 --- a/Source/PropertyChangedEventArgsHelper.cs +++ b/Source/PropertyChangedEventArgsHelper.cs @@ -175,9 +175,6 @@ namespace Nuclex.Support { /// /// Determines whether the property change affects the specified property /// - /// - /// Type of the property that will be tested for being affected - /// /// /// Property change that has been reported by the observed object /// diff --git a/Source/Semaphore.cs b/Source/Semaphore.cs index 1ea591a..49c85b4 100644 --- a/Source/Semaphore.cs +++ b/Source/Semaphore.cs @@ -56,10 +56,12 @@ namespace Nuclex.Support { /// become eligible for execution. /// /// -#if !(XBOX360 || WINDOWS_PHONE) +#if WINDOWS [Obsolete("Prefer the normal semaphore on Windows builds.")] -#endif + internal class Semaphore : WaitHandle { +#else public class Semaphore : WaitHandle { +#endif /// Initializes a new semaphore public Semaphore() { diff --git a/Source/Shared.cs b/Source/Shared.cs index 253cb9c..ac6726b 100644 --- a/Source/Shared.cs +++ b/Source/Shared.cs @@ -24,13 +24,13 @@ using System.Diagnostics; namespace Nuclex.Support { /// Manages a globally shared instance of the given Type - /// + /// /// Type of which a globally shared instance will be provided /// - public static class Shared where SharedType : new() { + public static class Shared where TShared : new() { /// Returns the global instance of the class - public static SharedType Instance { + public static TShared Instance { [DebuggerStepThrough] get { return instance; @@ -38,7 +38,7 @@ namespace Nuclex.Support { } /// Stored the globally shared instance - private static readonly SharedType instance = new SharedType(); + private static readonly TShared instance = new TShared(); } diff --git a/Source/StringBuilderHelper.Test.cs b/Source/StringBuilderHelper.Test.cs index e3a669a..19dae80 100644 --- a/Source/StringBuilderHelper.Test.cs +++ b/Source/StringBuilderHelper.Test.cs @@ -40,8 +40,13 @@ namespace Nuclex.Support { [Test] public void TestAppendByte() { StringBuilder builder = new StringBuilder(); - StringBuilderHelper.Append(builder, (byte)255); + builder.Append((byte)255, GarbagePolicy.Avoid); + Assert.AreEqual("255", builder.ToString()); + + builder.Clear(); + + builder.Append((byte)255, GarbagePolicy.Accept); Assert.AreEqual("255", builder.ToString()); } @@ -51,8 +56,13 @@ namespace Nuclex.Support { [Test] public void TestAppendNullByte() { StringBuilder builder = new StringBuilder(); - StringBuilderHelper.Append(builder, (byte)0); + builder.Append((byte)0, GarbagePolicy.Avoid); + Assert.AreEqual("0", builder.ToString()); + + builder.Clear(); + + builder.Append((byte)0, GarbagePolicy.Accept); Assert.AreEqual("0", builder.ToString()); } @@ -62,8 +72,13 @@ namespace Nuclex.Support { [Test] public void TestAppendPositiveInteger() { StringBuilder builder = new StringBuilder(); - StringBuilderHelper.Append(builder, 12345); + builder.Append(12345, GarbagePolicy.Avoid); + Assert.AreEqual("12345", builder.ToString()); + + builder.Clear(); + + builder.Append(12345, GarbagePolicy.Accept); Assert.AreEqual("12345", builder.ToString()); } @@ -73,8 +88,13 @@ namespace Nuclex.Support { [Test] public void TestAppendNullInteger() { StringBuilder builder = new StringBuilder(); - StringBuilderHelper.Append(builder, 0); + builder.Append(0, GarbagePolicy.Avoid); + Assert.AreEqual("0", builder.ToString()); + + builder.Clear(); + + builder.Append(0, GarbagePolicy.Accept); Assert.AreEqual("0", builder.ToString()); } @@ -84,8 +104,13 @@ namespace Nuclex.Support { [Test] public void TestAppendNegativeInteger() { StringBuilder builder = new StringBuilder(); - StringBuilderHelper.Append(builder, -12345); + builder.Append(-12345, GarbagePolicy.Avoid); + Assert.AreEqual("-12345", builder.ToString()); + + builder.Clear(); + + builder.Append(-12345, GarbagePolicy.Accept); Assert.AreEqual("-12345", builder.ToString()); } @@ -95,8 +120,13 @@ namespace Nuclex.Support { [Test] public void TestAppendPositiveLong() { StringBuilder builder = new StringBuilder(); - StringBuilderHelper.Append(builder, 12345L); + builder.Append(12345L, GarbagePolicy.Avoid); + Assert.AreEqual("12345", builder.ToString()); + + builder.Clear(); + + builder.Append(12345L, GarbagePolicy.Accept); Assert.AreEqual("12345", builder.ToString()); } @@ -106,8 +136,13 @@ namespace Nuclex.Support { [Test] public void TestAppendNullLong() { StringBuilder builder = new StringBuilder(); - StringBuilderHelper.Append(builder, 0L); + builder.Append(0L, GarbagePolicy.Avoid); + Assert.AreEqual("0", builder.ToString()); + + builder.Clear(); + + builder.Append(0L, GarbagePolicy.Accept); Assert.AreEqual("0", builder.ToString()); } @@ -117,8 +152,13 @@ namespace Nuclex.Support { [Test] public void TestAppendNegativeLong() { StringBuilder builder = new StringBuilder(); - StringBuilderHelper.Append(builder, -12345L); + builder.Append(-12345L, GarbagePolicy.Avoid); + Assert.AreEqual("-12345", builder.ToString()); + + builder.Clear(); + + builder.Append(-12345L, GarbagePolicy.Accept); Assert.AreEqual("-12345", builder.ToString()); } @@ -128,9 +168,14 @@ namespace Nuclex.Support { [Test] public void TestAppendNegativeFloat() { StringBuilder builder = new StringBuilder(); - StringBuilderHelper.Append(builder, -32.015625f); - Assert.AreEqual("-32.015625", builder.ToString()); + builder.Append(-0.125f, GarbagePolicy.Avoid); + Assert.AreEqual("-0.125", builder.ToString()); + + builder.Clear(); + + builder.Append(-0.125f, GarbagePolicy.Accept); + Assert.AreEqual("-0.125", builder.ToString()); } /// @@ -139,8 +184,13 @@ namespace Nuclex.Support { [Test] public void TestAppendPositiveFloat() { StringBuilder builder = new StringBuilder(); - StringBuilderHelper.Append(builder, 10.0625f); + builder.Append(10.0625f, GarbagePolicy.Avoid); + Assert.AreEqual("10.0625", builder.ToString()); + + builder.Clear(); + + builder.Append(10.0625f, GarbagePolicy.Accept); Assert.AreEqual("10.0625", builder.ToString()); } @@ -150,8 +200,13 @@ namespace Nuclex.Support { [Test] public void TestAppendSmallFloat() { StringBuilder builder = new StringBuilder(); - StringBuilderHelper.Append(builder, 0.00390625f); + builder.Append(0.00390625f, GarbagePolicy.Avoid); + Assert.AreEqual("0.00390625", builder.ToString()); + + builder.Clear(); + + builder.Append(0.00390625f, GarbagePolicy.Accept); Assert.AreEqual("0.00390625", builder.ToString()); } @@ -161,17 +216,21 @@ namespace Nuclex.Support { [Test] public void TestAppendHugeFloat() { StringBuilder builder = new StringBuilder(); - StringBuilderHelper.Append(builder, 1000000000.0f); + builder.Append(1000000000.0f, GarbagePolicy.Avoid); Assert.AreEqual("1000000000.0", builder.ToString()); + + builder.Clear(); + + builder.Append(1000000000.0f, GarbagePolicy.Accept); + Assert.AreEqual("1E+09", builder.ToString()); } /// Tests whether the number of decimal places can be restricted [Test] public void TestAppendFloatLimitDecimalPlaces() { StringBuilder builder = new StringBuilder(); - StringBuilderHelper.Append(builder, 0.00390625f, 3); - + builder.Append(0.00390625f, 3); Assert.AreEqual("0.003", builder.ToString()); } @@ -181,8 +240,7 @@ namespace Nuclex.Support { [Test] public void TestAppendFloatWithoutDecimalPlaces() { StringBuilder builder = new StringBuilder(); - StringBuilderHelper.Append(builder, 0.00390625f, 0); - + builder.Append(0.00390625f, 0); Assert.AreEqual("0", builder.ToString()); // Note: no rounding! } @@ -192,10 +250,10 @@ namespace Nuclex.Support { [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)); + Assert.IsFalse(builder.Append(float.PositiveInfinity, GarbagePolicy.Avoid)); + Assert.IsFalse(builder.Append(float.NegativeInfinity, GarbagePolicy.Avoid)); + Assert.IsFalse(builder.Append(float.NaN, GarbagePolicy.Avoid)); + Assert.IsFalse(builder.Append(0.000000059604644775390625f, GarbagePolicy.Avoid)); } /// @@ -205,8 +263,13 @@ namespace Nuclex.Support { [Test] public void TestAppendNegativeDouble() { StringBuilder builder = new StringBuilder(); - StringBuilderHelper.Append(builder, -32.015625); + builder.Append(-32.015625, GarbagePolicy.Avoid); + Assert.AreEqual("-32.015625", builder.ToString()); + + builder.Clear(); + + builder.Append(-32.015625, GarbagePolicy.Accept); Assert.AreEqual("-32.015625", builder.ToString()); } @@ -217,8 +280,13 @@ namespace Nuclex.Support { [Test] public void TestAppendPositiveDouble() { StringBuilder builder = new StringBuilder(); - StringBuilderHelper.Append(builder, 10.0625); + builder.Append(10.0625, GarbagePolicy.Avoid); + Assert.AreEqual("10.0625", builder.ToString()); + + builder.Clear(); + + builder.Append(10.0625, GarbagePolicy.Accept); Assert.AreEqual("10.0625", builder.ToString()); } @@ -229,8 +297,13 @@ namespace Nuclex.Support { [Test] public void TestAppendSmallDouble() { StringBuilder builder = new StringBuilder(); - StringBuilderHelper.Append(builder, 0.00390625); + builder.Append(0.00390625, GarbagePolicy.Avoid); + Assert.AreEqual("0.00390625", builder.ToString()); + + builder.Clear(); + + builder.Append(0.00390625, GarbagePolicy.Accept); Assert.AreEqual("0.00390625", builder.ToString()); } @@ -241,9 +314,14 @@ namespace Nuclex.Support { [Test] public void TestAppendHugeDouble() { StringBuilder builder = new StringBuilder(); - StringBuilderHelper.Append(builder, 1000000000000000000.0); + builder.Append(1000000000000000000.0, GarbagePolicy.Avoid); Assert.AreEqual("1000000000000000000.0", builder.ToString()); + + builder.Clear(); + + builder.Append(1000000000000000000.0, GarbagePolicy.Accept); + Assert.AreEqual("1E+18", builder.ToString()); } /// Tests whether the number of decimal places can be restricted @@ -273,12 +351,10 @@ namespace Nuclex.Support { [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) - ); + Assert.IsFalse(builder.Append(double.PositiveInfinity, GarbagePolicy.Avoid)); + Assert.IsFalse(builder.Append(double.NegativeInfinity, GarbagePolicy.Avoid)); + Assert.IsFalse(builder.Append(double.NaN, GarbagePolicy.Avoid)); + Assert.IsFalse(builder.Append(1.1102230246251565404236316680908e-16, GarbagePolicy.Avoid)); } /// @@ -291,7 +367,7 @@ namespace Nuclex.Support { Assert.AreEqual(string.Empty, builder.ToString()); } - + } } // namespace Nuclex.Support diff --git a/Source/StringBuilderHelper.cs b/Source/StringBuilderHelper.cs index f1468a4..fc81632 100644 --- a/Source/StringBuilderHelper.cs +++ b/Source/StringBuilderHelper.cs @@ -25,12 +25,6 @@ using System.Text; namespace Nuclex.Support { - /* - public enum Garbage { - Avoid, - Accept - } - */ /// Contains helper methods for the string builder class public static class StringBuilderHelper { @@ -50,13 +44,20 @@ namespace Nuclex.Support { /// /// String builder to which an integer will be appended /// Byte that will be appended to the string builder + /// How to behave regarding the garbage collector /// /// The normal StringBuilder.Append() method generates garbage when converting /// integer arguments whereas this method will avoid any garbage, albeit probably /// with a small performance impact compared to the built-in method. /// - public static void Append(StringBuilder builder, byte value) { - recursiveAppend(builder, value); + public static void Append( + this StringBuilder builder, byte value, GarbagePolicy garbagePolicy + ) { + if(garbagePolicy == GarbagePolicy.Avoid) { + recursiveAppend(builder, value); + } else { + builder.Append((int)value); + } } /// @@ -64,17 +65,24 @@ namespace Nuclex.Support { /// /// String builder to which an integer will be appended /// Integer that will be appended to the string builder + /// How to behave regarding the garbage collector /// /// The normal StringBuilder.Append() method generates garbage when converting /// integer arguments whereas this method will avoid any garbage, albeit probably /// with a small performance impact compared to the built-in method. /// - public static void Append(StringBuilder builder, int value) { - if (value < 0) { - builder.Append('-'); - recursiveAppend(builder, -value); + public static void Append( + this StringBuilder builder, int value, GarbagePolicy garbagePolicy + ) { + if(garbagePolicy == GarbagePolicy.Avoid) { + if(value < 0) { + builder.Append('-'); + recursiveAppend(builder, -value); + } else { + recursiveAppend(builder, value); + } } else { - recursiveAppend(builder, value); + builder.Append(value); } } @@ -83,17 +91,24 @@ namespace Nuclex.Support { /// /// String builder to which an integer will be appended /// Long integer that will be appended to the string builder + /// How to behave regarding the garbage collector /// /// The normal StringBuilder.Append() method generates garbage when converting /// integer arguments whereas this method will avoid any garbage, albeit probably /// with a small performance impact compared to the built-in method. /// - public static void Append(StringBuilder builder, long value) { - if (value < 0) { - builder.Append('-'); - recursiveAppend(builder, -value); + public static void Append( + this StringBuilder builder, long value, GarbagePolicy garbagePolicy + ) { + if(garbagePolicy == GarbagePolicy.Avoid) { + if(value < 0) { + builder.Append('-'); + recursiveAppend(builder, -value); + } else { + recursiveAppend(builder, value); + } } else { - recursiveAppend(builder, value); + builder.Append(value); } } @@ -102,14 +117,22 @@ namespace Nuclex.Support { /// /// String builder the value will be appended to /// Value that will be appended to the string builder + /// How to behave regarding the garbage collector /// 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) { - return Append(builder, value, int.MaxValue); + public static bool Append( + this StringBuilder builder, float value, GarbagePolicy garbagePolicy + ) { + if(garbagePolicy == GarbagePolicy.Avoid) { + return Append(builder, value, int.MaxValue); + } else { + builder.Append(value); + return true; + } } /// @@ -124,7 +147,7 @@ namespace Nuclex.Support { /// 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, int decimalPlaces) { + public static bool Append(this StringBuilder builder, float value, int decimalPlaces) { 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 @@ -142,9 +165,9 @@ namespace Nuclex.Support { int integral; int fractional; - if (exponent >= 0) { - if (exponent >= FractionalBitCount) { - if (exponent >= NumericBitCount) { + if(exponent >= 0) { + if(exponent >= FractionalBitCount) { + if(exponent >= NumericBitCount) { return false; } integral = mantissa << (exponent - FractionalBitCount); @@ -154,7 +177,7 @@ namespace Nuclex.Support { fractional = (mantissa << (exponent + 1)) & FractionalBits; } } else { - if (exponent < -FractionalBitCount) { + if(exponent < -FractionalBitCount) { return false; } integral = 0; @@ -162,30 +185,30 @@ namespace Nuclex.Support { } // Build the integral part - if (intValue < 0) { + if(intValue < 0) { builder.Append('-'); } - if (integral == 0) { + if(integral == 0) { builder.Append('0'); } else { recursiveAppend(builder, integral); } - if (decimalPlaces > 0) { + if(decimalPlaces > 0) { builder.Append('.'); // Build the fractional part - if (fractional == 0) { + if(fractional == 0) { builder.Append('0'); } else { - while (fractional != 0) { + while(fractional != 0) { fractional *= 10; int digit = (fractional >> FractionalBitCountPlusOne); builder.Append(numbers[digit]); fractional &= FractionalBits; --decimalPlaces; - if (decimalPlaces == 0) { + if(decimalPlaces == 0) { break; } } @@ -201,14 +224,22 @@ namespace Nuclex.Support { /// /// String builder the value will be appended to /// Value that will be appended to the string builder + /// How to behave regarding the garbage collector /// 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) { - return Append(builder, value, int.MaxValue); + public static bool Append( + this StringBuilder builder, double value, GarbagePolicy garbagePolicy + ) { + if(garbagePolicy == GarbagePolicy.Avoid) { + return Append(builder, value, int.MaxValue); + } else { + builder.Append(value); + return true; + } } /// @@ -224,7 +255,7 @@ namespace Nuclex.Support { /// 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, int decimalPlaces) { + public static bool Append(this StringBuilder builder, double value, int decimalPlaces) { 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 @@ -242,9 +273,9 @@ namespace Nuclex.Support { long integral; long fractional; - if (exponent >= 0) { - if (exponent >= FractionalBitCount) { - if (exponent >= NumericBitCount) { + if(exponent >= 0) { + if(exponent >= FractionalBitCount) { + if(exponent >= NumericBitCount) { return false; } integral = mantissa << (int)(exponent - FractionalBitCount); @@ -254,7 +285,7 @@ namespace Nuclex.Support { fractional = (mantissa << (int)(exponent + 1)) & FractionalBits; } } else { - if (exponent < -FractionalBitCount) { + if(exponent < -FractionalBitCount) { return false; } integral = 0; @@ -262,30 +293,30 @@ namespace Nuclex.Support { } // Build the integral part - if (longValue < 0) { + if(longValue < 0) { builder.Append('-'); } - if (integral == 0) { + if(integral == 0) { builder.Append('0'); } else { recursiveAppend(builder, integral); } - if (decimalPlaces > 0) { + if(decimalPlaces > 0) { builder.Append('.'); // Build the fractional part - if (fractional == 0) { + if(fractional == 0) { builder.Append('0'); } else { - while (fractional != 0) { + while(fractional != 0) { fractional *= 10; long digit = (fractional >> FractionalBitCountPlusOne); builder.Append(numbers[digit]); fractional &= FractionalBits; --decimalPlaces; - if (decimalPlaces == 0) { + if(decimalPlaces == 0) { break; } } @@ -307,7 +338,7 @@ namespace Nuclex.Support { int tenth = remaining / 10; #endif - if (tenth > 0) { + if(tenth > 0) { recursiveAppend(builder, tenth); } @@ -326,7 +357,7 @@ namespace Nuclex.Support { long tenth = remaining / 10; #endif - if (tenth > 0) { + if(tenth > 0) { recursiveAppend(builder, tenth); }