Skip to content

Commit

Permalink
Collections/PriorityQueue.cs:
Browse files Browse the repository at this point in the history
* Made a field readonly

IO/BinaryReaderWriter.cs:
IO/Tests/BinaryReaderWriter.cs:
+ Added support for reading and writing DateTime values that adjust their time
  zones automatically
* Changed the representation for standard DateTime values from 9 to 8 bytes
  (breaking change)

Mathematics/BigInteger.cs:
* Made the BitLength property setter private
* Fixed a bug in Multiply that could cause a crash with oversized data arrays
* Tweaked the Abs() implementation

Mathematics/FloatingPoint.cs:
Mathematics/Rational.cs:
Mathematics/documentation.xml:
* Tweaked documentation
  • Loading branch information
AdamMil committed Jun 15, 2018
1 parent e7927ce commit 9feaed1
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 24 deletions.
3 changes: 1 addition & 2 deletions Collections/PriorityQueue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ public PriorityQueue<T> Clone()
}

/// <summary>Removes and returns the element in the queue with the highest priority.</summary>
/// <returns>The element in the queue with the highest priority.</returns>
/// <exception cref="InvalidOperationException">Thrown if the collection is empty.</exception>
public T Dequeue()
{
Expand Down Expand Up @@ -329,7 +328,7 @@ void Heapify()
}

T[] array;
IComparer<T> cmp;
readonly IComparer<T> cmp;
int count, version;
}

Expand Down
100 changes: 95 additions & 5 deletions IO/BinaryReaderWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,17 @@ public void Read(void* dest, int count, int wordSize)
}
}

/// <summary>Reads a <see cref="DateTime"/> object from the binary reader with automatic time zone adjustment.
/// The value is assumed to have been written with <see cref="BinaryWriter.WriteAdjustableDateTime(DateTime)"/>.
/// </summary>
/// <remarks>If the value is based on the local time zone, it will be automatically adjusted to the new local time zone when
/// deserialized. (For instance, 3PM serialized in PST may become 6PM when deserialized in EST.) Times based on UTC are unaffected.
/// </remarks>
public DateTime ReadAdjustableDateTime()
{
return DateTime.FromBinary(ReadInt64());
}

/// <summary>Reads a one-byte boolean value from the stream.</summary>
public bool ReadBoolean()
{
Expand Down Expand Up @@ -486,7 +497,8 @@ public char ReadChar(Encoding encoding)
/// </summary>
public DateTime ReadDateTime()
{
return new DateTime(ReadInt64(), (DateTimeKind)ReadByte());
ulong value = ReadUInt64();
return new DateTime((long)(value>>2), (DateTimeKind)(value&3));
}

/// <summary>Reads a variable-length signed integer from the stream.</summary>
Expand Down Expand Up @@ -737,6 +749,27 @@ public int ReadChars(char* array, int count, Encoding encoding)
return totalRead;
}

/// <summary>Reads an array of <see cref="DateTime"/> objects from the stream with automatic time zone adjustment.</summary>
/// <remarks>If a value is based on the local time zone, it will be automatically adjusted to the new local time zone when
/// deserialized. (For instance, 3PM serialized in PST may become 6PM when deserialized in EST.) Times based on UTC are unaffected.
/// </remarks>
public DateTime[] ReadAdjustableDateTimes(int count)
{
DateTime[] data = new DateTime[count];
ReadAdjustableDateTimes(data, 0, count);
return data;
}

/// <summary>Reads an array of <see cref="DateTime"/> objects from the stream with automatic time zone adjustment.</summary>
/// <remarks>If a value is based on the local time zone, it will be automatically adjusted to the new local time zone when
/// deserialized. (For instance, 3PM serialized in PST may become 6PM when deserialized in EST.) Times based on UTC are unaffected.
/// </remarks>
public void ReadAdjustableDateTimes(DateTime[] array, int index, int count)
{
Utility.ValidateRange(array, index, count);
for(int end=index+count; index < end; index++) array[index] = ReadAdjustableDateTime();
}

/// <summary>Reads an array of <see cref="DateTime"/> objects from the stream.</summary>
public DateTime[] ReadDateTimes(int count)
{
Expand Down Expand Up @@ -957,6 +990,18 @@ public void ReadUInt64s(ulong* array, int count)
ReadInt64s((long*)array, count);
}

/// <summary>Reads a nullable <see cref="DateTime"/> object from the binary reader with automatic time zone adjustment.
/// The value is assumed to have been written with <see cref="BinaryWriter.WriteAdjustableDateTime(DateTime?)"/>.
/// </summary>
/// <remarks>If the value is based on the local time zone, it will be automatically adjusted to the new local time zone when
/// deserialized. (For instance, 3PM serialized in PST may become 6PM when deserialized in EST.) Times based on UTC are unaffected.
/// </remarks>
public DateTime? ReadNullableAdjustableDateTime()
{
if(!ReadBoolean()) return null;
else return ReadAdjustableDateTime();
}

/// <summary>Reads a one-byte nullable boolean value from the stream in the format written by <see cref="BinaryWriter.Write(bool?)"/>.</summary>
/// <remarks>The value is read as 0=false, 1=true, &gt;1=null.</remarks>
public bool? ReadNullableBoolean()
Expand Down Expand Up @@ -1836,13 +1881,13 @@ public int Write(char* data, int count, Encoding encoding)
return spaceNeeded;
}

/// <summary>Writes a <see cref="DateTime"/> to the stream as an sequence of 9 bytes. The <see cref="DateTime"/> may later be
/// <summary>Writes a <see cref="DateTime"/> to the stream as a sequence of 8 bytes. The <see cref="DateTime"/> may later be
/// read with <see cref="BinaryReader.ReadDateTime"/>.
/// </summary>
public void Write(DateTime dateTime)
{
Write(dateTime.Ticks);
Write((byte)dateTime.Kind);
// Ticks requires 62 bits and Kind requires 2 bits, so the whole thing can be packed into 64 bits
Write(((ulong)dateTime.Ticks << 2) | (uint)dateTime.Kind);
}

/// <summary>Writes a nullable <see cref="DateTime"/> to the stream as a boolean potentially followed by the value.</summary>
Expand All @@ -1866,7 +1911,7 @@ public void Write(DateTime[] dateTimes, int index, int count)
for(int end = index+count; index < end; index++) Write(dateTimes[index]);
}

/// <summary>Writes a <see cref="Guid"/> to the stream as an sequence of 16 bytes. The <see cref="Guid"/> may later be read
/// <summary>Writes a <see cref="Guid"/> to the stream as a sequence of 16 bytes. The <see cref="Guid"/> may later be read
/// with <see cref="BinaryReader.ReadGuid"/>.
/// </summary>
public unsafe void Write(Guid guid)
Expand Down Expand Up @@ -2147,6 +2192,51 @@ public void Write(void* data, int count, int wordSize)
WriteCore((byte*)data, count, wordSize);
}

/// <summary>Writes a <see cref="DateTime"/> value with automatic time zone adjustment. This takes 8 bytes.</summary>
/// <remarks>If the value is based on the local time zone, it will be automatically adjusted to the new local time zone when
/// deserialized. (For instance, 3PM serialized in PST may become 6PM when deserialized in EST.) Times based on UTC are unaffected.
/// The value can be deserialized with <see cref="BinaryReader.ReadAdjustableDateTime"/>.
/// </remarks>
public void WriteAdjustableDateTime(DateTime value)
{
Write(value.ToBinary());
}

/// <summary>Writes a nullable <see cref="DateTime"/> value as a boolean potentially followed by the value, with automatic time zone
/// adjustment.
/// </summary>
/// <remarks>If the value is based on the local time zone, it will be automatically adjusted to the new local time zone when
/// deserialized. (For instance, 3PM serialized in PST may become 6PM when deserialized in EST.) Times based on UTC are unaffected.
/// The value can be deserialized with <see cref="BinaryReader.ReadNullableAdjustableDateTime"/>.
/// </remarks>
public void WriteAdjustableDateTime(DateTime? value)
{
Write(value.HasValue);
if(value.HasValue) WriteAdjustableDateTime(value.GetValueOrDefault());
}

/// <summary>Writes an array of <see cref="DateTime"/> objects to the stream with automatic time zone adjustment.</summary>
/// <remarks>If a value is based on the local time zone, it will be automatically adjusted to the new local time zone when
/// deserialized. (For instance, 3PM serialized in PST may become 6PM when deserialized in EST.) Times based on UTC are unaffected.
/// The values can be deserialized with <see cref="BinaryReader.ReadAdjustableDateTimes"/>.
/// </remarks>
public void WriteAdjustableDateTimes(DateTime[] dateTimes)
{
if(dateTimes == null) throw new ArgumentNullException();
WriteAdjustableDateTimes(dateTimes, 0, dateTimes.Length);
}

/// <summary>Writes an array of <see cref="DateTime"/> objects to the stream with automatic time zone adjustment.</summary>
/// <remarks>If a value is based on the local time zone, it will be automatically adjusted to the new local time zone when
/// deserialized. (For instance, 3PM serialized in PST may become 6PM when deserialized in EST.) Times based on UTC are unaffected.
/// The values can be deserialized with <see cref="BinaryReader.ReadAdjustableDateTimes"/>.
/// </remarks>
public void WriteAdjustableDateTimes(DateTime[] dateTimes, int index, int count)
{
Utility.ValidateRange(dateTimes, index, count);
for(int end = index+count; index < end; index++) WriteAdjustableDateTime(dateTimes[index]);
}

/// <summary>Writes a signed integer with a variable-length format, taking from one to five bytes.</summary>
public void WriteEncoded(int value)
{
Expand Down
4 changes: 4 additions & 0 deletions IO/Tests/BinaryReaderWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public void TestBasics()
w.Write(new byte[] { 1, 2, 3 });
w.Write(dt1);
w.Write(new DateTime[] { dt2, dt1 });
w.WriteAdjustableDateTime(dt1);
w.WriteAdjustableDateTimes(new DateTime[] { dt2, dt1 });
w.Write(3.14159m);
w.Write(new decimal[] { 2000.77777m, -10000000000000.55m });
w.Write(Math.PI);
Expand Down Expand Up @@ -74,6 +76,8 @@ public void TestBasics()
TestHelpers.AssertArrayEquals(r.ReadBytes(3), new byte[] { 1, 2, 3 });
Assert.AreEqual(dt1, r.ReadDateTime());
TestHelpers.AssertArrayEquals(r.ReadDateTimes(2), new DateTime[] { dt2, dt1 });
Assert.AreEqual(dt1, r.ReadAdjustableDateTime());
TestHelpers.AssertArrayEquals(r.ReadAdjustableDateTimes(2), new DateTime[] { dt2, dt1 });
Assert.AreEqual(3.14159m, r.ReadDecimal());
TestHelpers.AssertArrayEquals(r.ReadDecimals(2), new decimal[] { 2000.77777m, -10000000000000.55m });
Assert.AreEqual(Math.PI, r.ReadDouble());
Expand Down
14 changes: 6 additions & 8 deletions Mathematics/BigInteger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ internal Integer(uint[] array, bool negative, bool clone)
public int BitLength
{
get { return (int)(info & 0x7FFFFFFF); }
set { info = value == 0 ? 0 : info & SignBit | (uint)value; }
private set { info = value == 0 ? 0 : info & SignBit | (uint)value; }
}

/// <summary>Determines whether this <see cref="Integer"/> value is even. Zero is considered even.</summary>
Expand Down Expand Up @@ -331,8 +331,7 @@ public int Sign
/// <summary>Returns an <see cref="Integer"/> value with the same magnitude and a non-negative sign.</summary>
public Integer Abs()
{
if(IsNegative) return new Integer(data, info ^ SignBit);
else return this;
return new Integer(data, info & ~SignBit);
}

/// <summary>Returns a new <see cref="Integer"/> with the same value but whose internal storage is newly allocated, allowing it to be
Expand Down Expand Up @@ -1793,8 +1792,7 @@ public static explicit operator decimal(Integer value)
/// <summary>Returns the absolute value of the given <see cref="Integer"/>.</summary>
public static Integer Abs(Integer value)
{
if(value.IsNegative) return new Integer(value.data, value.info ^ SignBit);
else return value;
return new Integer(value.data, value.info & ~SignBit);
}

/// <summary>Counts the number of trailing zero bits in the binary representation of the integer.</summary>
Expand Down Expand Up @@ -3403,18 +3401,18 @@ static uint[] MultiplyMagnitudes(Integer a, Integer b)
if(maxBitLength < 0) throw new OverflowException();
uint[] result = new uint[GetElementCount(maxBitLength)];

for(int ori=0, be=b.GetElementCount(), ai=0; ai<a.data.Length; ori++, ai++)
for(int ae=a.GetElementCount(), be=b.GetElementCount(), ai=0; ai<ae; ai++)
{
if(a.data[ai] == 0) continue;

ulong carry = 0;
for(int ri=ori, bi=0; bi<b.data.Length; ri++, bi++)
for(int ri=ai, bi=0; bi<be; ri++, bi++)
{
carry += (ulong)a.data[ai] * b.data[bi] + result[ri];
result[ri] = (uint)carry;
carry >>= 32;
}
if(carry != 0) result[ori+b.data.Length] = (uint)carry;
if(carry != 0) result[ai+be] = (uint)carry;
}
return result;
}
Expand Down
2 changes: 1 addition & 1 deletion Mathematics/FloatingPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2440,7 +2440,7 @@ public static FP107 Sqrt(FP107 value)
{
if(value.IsPositive)
{
// the usual method is to Newton's method to solve x^2 = a. we represent it as a function with a zero at the answer:
// the usual method is to use Newton's method to solve x^2 = a. we represent it as a function with a zero at the answer:
// f(x) = x^2 - a, which has a derivative f'(x) = 2x. and then Newton's method is x1 = x0 - f(x0)/f'(x0), yielding an iteration of
// x1 ~= x0 - (x0^2-a)/2x0. thus if x ~= sqrt(a), then x - (x^2-a)/2x is an approximation that's twice as good
//
Expand Down
16 changes: 9 additions & 7 deletions Mathematics/Rational.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ namespace AdamMil.Mathematics
/// <para>To get a <see cref="Rational"/> value exactly equal to 1.1, any of the following can be used instead, in order from fastest
/// to slowest: <c>x = Rational.FromComponents(11, 10)</c>, <c>x = new Rational(11, 10)</c>, <c>x = (Rational)1.1m</c>,
/// <c>x = Rational.Parse("1.1", CultureInfo.InvariantCulture)</c>, or <c>x = Rational.FromDecimalApproximation(1.1)</c>. The first is
/// very fast. The last is especially slow slow and should not be executed often, and the method that uses a <see cref="decimal"/>
/// very fast. The last is especially slow and should not be executed often, and the method that uses a <see cref="decimal"/>
/// literal (1.1m in this case) is only suitable if the value can be represented exactly by a <see cref="decimal"/> value.
/// </para>
/// <para>
Expand Down Expand Up @@ -97,7 +97,8 @@ public Rational(Integer value)

/// <summary>Initializes a new <see cref="Rational"/> value from a floating-point value.</summary>
/// <remarks>The rational will be exactly equal to the floating-point value, but this may not be what you want since floating-point
/// numbers are usually approximations of decimal numbers.
/// numbers are usually approximations of decimal numbers. You can use the <see cref="Approximate(int)"/> method to produce a nearby
/// simpler number.
/// </remarks>
public Rational(double value)
{
Expand Down Expand Up @@ -128,7 +129,8 @@ public Rational(double value)

/// <summary>Initializes a new <see cref="Rational"/> value from a floating-point value.</summary>
/// <remarks>The rational will be exactly equal to the floating-point value, but this may not be what you want since floating-point
/// numbers are usually approximations of decimal numbers.
/// numbers are usually approximations of decimal numbers. You can use the <see cref="Approximate(int)"/> method to produce a nearby
/// simpler number.
/// </remarks>
public Rational(FP107 value)
{
Expand Down Expand Up @@ -159,7 +161,7 @@ public Rational(FP107 value)
public Rational(decimal value) : this(value, true) { }

/// <summary>Initializes a new <see cref="Rational"/> value from a decimal value, with an option to skip simplification.</summary>
/// <remarks>Normally, you should use the <see cref="Rational(FP107)"/> constructor instead, but this constructor may be useful with
/// <remarks>Normally, you should use the <see cref="Rational(decimal)"/> constructor instead, but this constructor may be useful with
/// unsimplified operations.
/// <include file="documentation.xml" path="/Rational/UnsimplifiedRemarks/remarks/node()"/>
/// </remarks>
Expand Down Expand Up @@ -1191,15 +1193,15 @@ public static Integer Floor(Rational value)
return i;
}

/// <summary>This method used to construct a <see cref="Rational"/> from the <see cref="Numerator"/> and <see cref="Denominator"/>
/// <summary>This method is used to construct a <see cref="Rational"/> from the <see cref="Numerator"/> and <see cref="Denominator"/>
/// obtained from an existing <see cref="Rational"/>.
/// </summary>
/// <param name="numerator">The numerator of the ratio, which must not share any factors with <paramref name="denominator"/>.</param>
/// <param name="denominator">The denominator of the ratio, which must be positive and must not share any factors with
/// <paramref name="numerator"/>.
/// </param>
/// <remarks>This method differs from the <see cref="Rational(Integer,Integer)"/> constructor in that the values are not checked for
/// sign or range, and are not divided by their greatest common factor. You must do those yourself.
/// sign or range, and are not divided by their greatest common factor. You must do those yourself if necessary.
/// </remarks>
public static Rational FromComponents(Integer numerator, Integer denominator)
{
Expand Down Expand Up @@ -1229,7 +1231,7 @@ public static Rational FromContinuedFraction(IList<int> continuedFraction)
public static Rational FromContinuedFraction(IList<int> continuedFraction, int maxTerms)
{
if(continuedFraction == null) throw new ArgumentNullException();
if(maxTerms <= 0) throw new ArgumentOutOfRangeException();
if(maxTerms <= 0) throw new ArgumentOutOfRangeException("There must be at least one coefficient.");
if(maxTerms > continuedFraction.Count) maxTerms = continuedFraction.Count;

// to convert a continued fraction to a number, you start at the end (x = c[N-1]) and for each preceding term, you set x = c[i] + 1/x
Expand Down
2 changes: 1 addition & 1 deletion Mathematics/documentation.xml
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@
that the result will not have a magnitude equal to any operand. In the example above, <c>b *= a</c> would be safe if you know
that neither operand equals 1 or -1. (Zero values never share internal storage.)
</para>
<para>Finally, don't rely on unsafe operations to do the operation in-place. Sometimes they may have to allocate new storage.</para>
<para>Finally, don't assume unsafe operations always do the operation in-place. Sometimes they have to allocate new storage.</para>
</note>
</remarks>
</UnsafeOps>
Expand Down

0 comments on commit 9feaed1

Please sign in to comment.