diff --git a/Documents/DoubleConverter.md b/Documents/DoubleConverter.md new file mode 100644 index 0000000..21af878 --- /dev/null +++ b/Documents/DoubleConverter.md @@ -0,0 +1,229 @@ +Double Converter +================ + +Originally from http://www.yoda.arachsys.com/csharp/DoubleConverter.cs + +This class converts doubles to strings without relying on the current locale. +The problem itself is quite deep and in the C++ side (including the supporting +libraries used to implement the .NET BCL), various techniques such as Dragon4, +Ryu and DragonBox are used. + +The main problems are a) speed and b) full round-trip support such that a float +converted to a string (even if the decimal places are an infinite series or +have been rounded) will parse back to the exact same floating point number. + + 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/Documents/DoubleConverter.txt b/Documents/DoubleConverter.txt deleted file mode 100644 index 370bed8..0000000 --- a/Documents/DoubleConverter.txt +++ /dev/null @@ -1,218 +0,0 @@ -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/Documents/Request Framework.md b/Documents/Request Framework.md new file mode 100644 index 0000000..610d31a --- /dev/null +++ b/Documents/Request Framework.md @@ -0,0 +1,184 @@ +Request Framework +================= + +These are just some ponderings on the design. Most of what I was trying to accomplish +via my request framework has long since been covered by System.Threading.Tasks and +most of what I designed is not in the separate and forgotten Nuclex.Support.Transactions +library. + + +Ponderings +---------- + +The request framework should not require that .NET multithreading is used for +asynchronous requests. + +Otherwise, it would prvent overlapped operations, 3rd party APIs (eg. used +via P/Invoke) from being able to use the request framework and possibly even +spawn duplicate implementations. + + +Design using interfaces +----------------------- + + interface IWaitable { + + /// Fired when the background process has finished + /// + /// If the process is already finished when a client registers to this event, + /// the registered callback will be invoked synchronously right when the + /// registration takes place. + /// + event EventHandler Finished; + + /// Waits until the background process finishes + void Wait(); + + /// Waits until the background process finishes or a timeout occurs + /// + /// Number of milliseconds after which to stop waiting and return immediately + /// + /// + /// True if the background process completed, false if the timeout was reached + /// + bool Wait(int timeoutMilliseconds); + + /// Whether the background process has finished + bool Finished { get; } + + // ? + /// Wait handle that can be used to wait for multiple waitables + /// + /// Only use the WaitHandle to wait if you're running in a different thread than + /// the request, or you may deadlock. Libraries based on single-threaded + /// multitasking may employ concepts such as window message processing to achieve + /// parallelism which could be stalled by improper waiting using the WaitHandle + /// whereas the Wait() method typically has a suitable implementation compatible + /// with the request's requirements. + /// + WaitHandle WaitHandle { get; } + + } + + interface IThreadedWaitable : IWaitable { + + WaitHandle WaitHandle { get; } + + } + + interface IRequest : IWaitable { + + /// + /// Waits for the background process to complete and re-throws the exception to + /// the caller when an error has occured + /// + void Join(); + + } + + interface IRequest : IRequest { + + /// + /// Waits for the background process to complete and re-throws the exception to + /// the caller when an error has occured + /// + /// The result of the background processing + new ResultType Join(); + + } + + interface IThreadedRequest : IRequest, IThreadedWaitable { } + + interface IThreadedRequest : IRequest, IThreadedRequest { } + + +Impossible implementation +------------------------- + + class Request : IRequest { + + event EventHandler Finished; + void Wait(); + bool Wait(int timeoutMilliseconds); + bool Finished { get; } + void Join(); + protected virtual void ReraiseExceptions() { } + + } + + class Request : Request, IRequest { + + new ResultType Join(); + protected abstract ResultType GatherResults(); + + } + +Do not provide: (see conflict in second version) + + class ThreadedRequest : Request, IThreadedRequest { + + WaitHandle WaitHandle { get; } + + } + + class ThreadedRequest : ThreadedRequest, Request { } + + // However, not providing these, the user would have to rewrite + // the complex threading routines everywhere he uses then. Bad. + + +Inelegant implementation +------------------------ + + class Void {} + + class Request : IRequest { + + new ResultType Join(); + protected abstract ResultType GatherResults(); + + } + + class ThreadedRequest : Request { } + + // However, not providing these, the user would have to rewrite + // the complex threading routines everywhere he uses then. Bad. + +Maybe keeping threaded and non-threaded requests apart is a good thing? + + IWaitable (without Finished event) + Waitable (Finished event) + Request + Request + + IWaitable (without Finished event) + ThreadedWaitable (AsyncFinished event) + ThreadedRequest + ThreadedRequest + +Or just dump the WaitHandle schmonder + + Waitable (with virtual protected SyncRoot { get { return this; } }) + Request + Request + + LazyWaitHandle + WaitHandle Get(Waitable waitable) + +Or use policy classes (waithandle trouble) + + Waitable + Request + Request + + RequestImpl + RequestImpl + + LazyWaitHandle + WaitHandle Get(Waitable waitable) + WaitHandle Get(Waitable waitable, object syncRoot) + + ThreadPolicy { + virtual void lock() {} + virtual void unlock() {} + } diff --git a/Documents/Request Framework.txt b/Documents/Request Framework.txt deleted file mode 100644 index d8feaa0..0000000 --- a/Documents/Request Framework.txt +++ /dev/null @@ -1,172 +0,0 @@ -The request framework should not require that .NET multithreading is used for -asynchronous requests. - -Otherwise, it would prvent overlapped operations, 3rd party APIs (eg. used -via P/Invoke) from being able to use the request framework and possibly even -spawn duplicate implementations. - - -Design using interfaces: - - interface IWaitable { - - /// Fired when the background process has finished - /// - /// If the process is already finished when a client registers to this event, - /// the registered callback will be invoked synchronously right when the - /// registration takes place. - /// - event EventHandler Finished; - - /// Waits until the background process finishes - void Wait(); - - /// Waits until the background process finishes or a timeout occurs - /// - /// Number of milliseconds after which to stop waiting and return immediately - /// - /// - /// True if the background process completed, false if the timeout was reached - /// - bool Wait(int timeoutMilliseconds); - - /// Whether the background process has finished - bool Finished { get; } - -// ? - /// Wait handle that can be used to wait for multiple waitables - /// - /// Only use the WaitHandle to wait if you're running in a different thread than - /// the request, or you may deadlock. Libraries based on single-threaded - /// multitasking may employ concepts such as window message processing to achieve - /// parallelism which could be stalled by improper waiting using the WaitHandle - /// whereas the Wait() method typically has a suitable implementation compatible - /// with the request's requirements. - /// - WaitHandle WaitHandle { get; } - - } - - interface IThreadedWaitable : IWaitable { - - WaitHandle WaitHandle { get; } - - } - - interface IRequest : IWaitable { - - /// - /// Waits for the background process to complete and re-throws the exception to - /// the caller when an error has occured - /// - void Join(); - - } - - interface IRequest : IRequest { - - /// - /// Waits for the background process to complete and re-throws the exception to - /// the caller when an error has occured - /// - /// The result of the background processing - new ResultType Join(); - - } - - interface IThreadedRequest : IRequest, IThreadedWaitable { } - - interface IThreadedRequest : IRequest, IThreadedRequest { } - - -Impossible implementation: - - class Request : IRequest { - - event EventHandler Finished; - void Wait(); - bool Wait(int timeoutMilliseconds); - bool Finished { get; } - void Join(); - protected virtual void ReraiseExceptions() { } - - } - - class Request : Request, IRequest { - - new ResultType Join(); - protected abstract ResultType GatherResults(); - - } - - Do not provide: (see conflict in second version) - - class ThreadedRequest : Request, IThreadedRequest { - - WaitHandle WaitHandle { get; } - - } - - class ThreadedRequest : ThreadedRequest, Request { } - - // However, not providing these, the user would have to rewrite - // the complex threading routines everywhere he uses then. Bad. - -Inelegant implementation: - - class Void {} - - class Request : IRequest { - - new ResultType Join(); - protected abstract ResultType GatherResults(); - - } - - class ThreadedRequest : Request { } - - // However, not providing these, the user would have to rewrite - // the complex threading routines everywhere he uses then. Bad. - - - -Maybe keeping threaded and non-threaded requests apart is a good thing? - - IWaitable (without Finished event) - Waitable (Finished event) - Request - Request - - IWaitable (without Finished event) - ThreadedWaitable (AsyncFinished event) - ThreadedRequest - ThreadedRequest - - -Or just dump the WaitHandle schmonder - - Waitable (with virtual protected SyncRoot { get { return this; } }) - Request - Request - - LazyWaitHandle - WaitHandle Get(Waitable waitable) - - -Or use policy classes (waithandle trouble) - - Waitable - Request - Request - - RequestImpl - RequestImpl - - LazyWaitHandle - WaitHandle Get(Waitable waitable) - WaitHandle Get(Waitable waitable, object syncRoot) - - ThreadPolicy { - virtual void lock() {} - virtual void unlock() {} - } diff --git a/Documents/Nuclex.Support.txt b/Documents/ShuffleTableGenerator.md similarity index 56% rename from Documents/Nuclex.Support.txt rename to Documents/ShuffleTableGenerator.md index 4e29d84..4dd3710 100644 --- a/Documents/Nuclex.Support.txt +++ b/Documents/ShuffleTableGenerator.md @@ -1,5 +1,15 @@ - #if DEBUG - +Shuffle Table Generator +======================= + +The `LicenseKey` class converts a GUID (or any 128 bits of data) into a license key similar +to the ones used by Microsoft products (5 groups of 5 alphanumeric characters). + +For additional obfuscation, bits can be mapped in a random order. That will offer some +protection when you have, for example, a running serial number. With shuffling, each +increment will result in likely several alphanumeric characters changing in seemingly +unpredictable ways (base-2 to base-36 conversion pretty much guarantees that any small +change affects many following alphanumeric "digits"). + /// Generates a new random shuffle table /// /// Number of iterations in which to randomize the shuffle table @@ -23,6 +33,3 @@ return shuffleTable; } - - #endif -