Converted all documents to Markdown

This commit is contained in:
Markus Ewald 2025-06-19 12:35:43 +02:00
parent e146057071
commit 5fb433ad98
5 changed files with 425 additions and 395 deletions

View File

@ -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;
/// <summary>
/// A class to allow the conversion of doubles to string representations of
/// their exact decimal values. The implementation aims for readability over
/// efficiency.
/// </summary>
public class DoubleConverter
{
/// <summary>
/// Converts the given double to a string representation of its
/// exact decimal value.
/// </summary>
/// <param name="d">The double to convert.</param>
/// <returns>A string representation of the double's exact decimal value.</return>
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();
}
/// <summary>Private class used for manipulating
class ArbitraryDecimal
{
/// <summary>Digits in the decimal expansion, one byte per digit
byte[] digits;
/// <summary>
/// How many digits are *after* the decimal point
/// </summary>
int decimalPoint=0;
/// <summary>
/// Constructs an arbitrary decimal expansion from the given long.
/// The long must not be negative.
/// </summary>
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();
}
/// <summary>
/// Multiplies the current expansion by the given amount, which should
/// only be 2 or 5.
/// </summary>
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();
}
/// <summary>
/// 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.
/// </summary>
internal void Shift (int amount)
{
decimalPoint += amount;
}
/// <summary>
/// Removes leading/trailing zeroes from the expansion.
/// </summary>
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;
}
/// <summary>
/// Converts the value to a proper decimal string representation.
/// </summary>
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);
}
}
}

View File

@ -1,218 +0,0 @@
From http://www.yoda.arachsys.com/csharp/DoubleConverter.cs
using System;
using System.Globalization;
/// <summary>
/// A class to allow the conversion of doubles to string representations of
/// their exact decimal values. The implementation aims for readability over
/// efficiency.
/// </summary>
public class DoubleConverter
{
/// <summary>
/// Converts the given double to a string representation of its
/// exact decimal value.
/// </summary>
/// <param name="d">The double to convert.</param>
/// <returns>A string representation of the double's exact decimal value.</return>
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();
}
/// <summary>Private class used for manipulating
class ArbitraryDecimal
{
/// <summary>Digits in the decimal expansion, one byte per digit
byte[] digits;
/// <summary>
/// How many digits are *after* the decimal point
/// </summary>
int decimalPoint=0;
/// <summary>
/// Constructs an arbitrary decimal expansion from the given long.
/// The long must not be negative.
/// </summary>
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();
}
/// <summary>
/// Multiplies the current expansion by the given amount, which should
/// only be 2 or 5.
/// </summary>
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();
}
/// <summary>
/// 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.
/// </summary>
internal void Shift (int amount)
{
decimalPoint += amount;
}
/// <summary>
/// Removes leading/trailing zeroes from the expansion.
/// </summary>
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;
}
/// <summary>
/// Converts the value to a proper decimal string representation.
/// </summary>
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);
}
}
}

View File

@ -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 {
/// <summary>Fired when the background process has finished</summary>
/// <remarks>
/// 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.
/// </remarks>
event EventHandler Finished;
/// <summary>Waits until the background process finishes</summary>
void Wait();
/// <summary>Waits until the background process finishes or a timeout occurs</summary>
/// <param name="timeoutMilliseconds">
/// Number of milliseconds after which to stop waiting and return immediately
/// </param>
/// <returns>
/// True if the background process completed, false if the timeout was reached
/// </returns>
bool Wait(int timeoutMilliseconds);
/// <summary>Whether the background process has finished</summary>
bool Finished { get; }
// ?
/// <summary>Wait handle that can be used to wait for multiple waitables</summary>
/// <remarks>
/// 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.
/// </remarks>
WaitHandle WaitHandle { get; }
}
interface IThreadedWaitable : IWaitable {
WaitHandle WaitHandle { get; }
}
interface IRequest : IWaitable {
/// <summary>
/// Waits for the background process to complete and re-throws the exception to
/// the caller when an error has occured
/// </summary>
void Join();
}
interface IRequest<ResultType> : IRequest {
/// <summary>
/// Waits for the background process to complete and re-throws the exception to
/// the caller when an error has occured
/// </summary>
/// <returns>The result of the background processing</returns>
new ResultType Join();
}
interface IThreadedRequest : IRequest, IThreadedWaitable { }
interface IThreadedRequest<ResultType> : IRequest<ResultType>, 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<ResultType> : Request, IRequest<ResultType> {
new ResultType Join();
protected abstract ResultType GatherResults();
}
Do not provide: (see conflict in second version)
class ThreadedRequest : Request, IThreadedRequest {
WaitHandle WaitHandle { get; }
}
class ThreadedRequest<ResultType> : ThreadedRequest, Request<ResultType> { }
// 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<ResultType> : IRequest<ResultType> {
new ResultType Join();
protected abstract ResultType GatherResults();
}
class ThreadedRequest<ResultType> : Request<ResultType> { }
// 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<Bla>
IWaitable (without Finished event)
ThreadedWaitable (AsyncFinished event)
ThreadedRequest
ThreadedRequest<Bla>
Or just dump the WaitHandle schmonder
Waitable (with virtual protected SyncRoot { get { return this; } })
Request
Request<Bla>
LazyWaitHandle
WaitHandle Get(Waitable waitable)
Or use policy classes (waithandle trouble)
Waitable
Request
Request<Bla>
RequestImpl<ThreadPolicy>
RequestImpl<Bla, ThreadPolicy>
LazyWaitHandle
WaitHandle Get(Waitable waitable)
WaitHandle Get(Waitable waitable, object syncRoot)
ThreadPolicy {
virtual void lock() {}
virtual void unlock() {}
}

View File

@ -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 {
/// <summary>Fired when the background process has finished</summary>
/// <remarks>
/// 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.
/// </remarks>
event EventHandler Finished;
/// <summary>Waits until the background process finishes</summary>
void Wait();
/// <summary>Waits until the background process finishes or a timeout occurs</summary>
/// <param name="timeoutMilliseconds">
/// Number of milliseconds after which to stop waiting and return immediately
/// </param>
/// <returns>
/// True if the background process completed, false if the timeout was reached
/// </returns>
bool Wait(int timeoutMilliseconds);
/// <summary>Whether the background process has finished</summary>
bool Finished { get; }
// ?
/// <summary>Wait handle that can be used to wait for multiple waitables</summary>
/// <remarks>
/// 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.
/// </remarks>
WaitHandle WaitHandle { get; }
}
interface IThreadedWaitable : IWaitable {
WaitHandle WaitHandle { get; }
}
interface IRequest : IWaitable {
/// <summary>
/// Waits for the background process to complete and re-throws the exception to
/// the caller when an error has occured
/// </summary>
void Join();
}
interface IRequest<ResultType> : IRequest {
/// <summary>
/// Waits for the background process to complete and re-throws the exception to
/// the caller when an error has occured
/// </summary>
/// <returns>The result of the background processing</returns>
new ResultType Join();
}
interface IThreadedRequest : IRequest, IThreadedWaitable { }
interface IThreadedRequest<ResultType> : IRequest<ResultType>, 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<ResultType> : Request, IRequest<ResultType> {
new ResultType Join();
protected abstract ResultType GatherResults();
}
Do not provide: (see conflict in second version)
class ThreadedRequest : Request, IThreadedRequest {
WaitHandle WaitHandle { get; }
}
class ThreadedRequest<ResultType> : ThreadedRequest, Request<ResultType> { }
// 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<ResultType> : IRequest<ResultType> {
new ResultType Join();
protected abstract ResultType GatherResults();
}
class ThreadedRequest<ResultType> : Request<ResultType> { }
// 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<Bla>
IWaitable (without Finished event)
ThreadedWaitable (AsyncFinished event)
ThreadedRequest
ThreadedRequest<Bla>
Or just dump the WaitHandle schmonder
Waitable (with virtual protected SyncRoot { get { return this; } })
Request
Request<Bla>
LazyWaitHandle
WaitHandle Get(Waitable waitable)
Or use policy classes (waithandle trouble)
Waitable
Request
Request<Bla>
RequestImpl<ThreadPolicy>
RequestImpl<Bla, ThreadPolicy>
LazyWaitHandle
WaitHandle Get(Waitable waitable)
WaitHandle Get(Waitable waitable, object syncRoot)
ThreadPolicy {
virtual void lock() {}
virtual void unlock() {}
}

View File

@ -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").
/// <summary>Generates a new random shuffle table</summary> /// <summary>Generates a new random shuffle table</summary>
/// <param name="iterationCount"> /// <param name="iterationCount">
/// Number of iterations in which to randomize the shuffle table /// Number of iterations in which to randomize the shuffle table
@ -23,6 +33,3 @@
return shuffleTable; return shuffleTable;
} }
#endif