mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-18 10:38:05 +00:00
code reviews
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public class TValueTests
|
||||
@@ -42,7 +44,7 @@ public class TValueTests
|
||||
var dt = new DateTime(2023, 1, 1, 12, 0, 0, DateTimeKind.Utc);
|
||||
var tValue = new TValue(dt.Ticks, 123.456);
|
||||
|
||||
string result = tValue.ToString();
|
||||
string result = tValue.ToString(null, CultureInfo.InvariantCulture);
|
||||
|
||||
Assert.Contains("2023-01-01", result, StringComparison.Ordinal);
|
||||
Assert.Contains("12:00:00", result, StringComparison.Ordinal);
|
||||
@@ -288,7 +290,7 @@ public class TValueTests
|
||||
var dt = new DateTime(2023, 1, 1, 12, 0, 0, DateTimeKind.Utc);
|
||||
var tValue = new TValue(dt.Ticks, double.NaN);
|
||||
|
||||
string result = tValue.ToString();
|
||||
string result = tValue.ToString(null, CultureInfo.InvariantCulture);
|
||||
|
||||
Assert.Contains("NaN", result, StringComparison.Ordinal);
|
||||
}
|
||||
@@ -299,7 +301,7 @@ public class TValueTests
|
||||
var dt = new DateTime(2023, 1, 1, 12, 0, 0, DateTimeKind.Utc);
|
||||
var tValue = new TValue(dt.Ticks, double.PositiveInfinity);
|
||||
|
||||
string result = tValue.ToString();
|
||||
string result = tValue.ToString(null, CultureInfo.InvariantCulture);
|
||||
|
||||
Assert.Contains("∞", result, StringComparison.Ordinal);
|
||||
}
|
||||
@@ -310,7 +312,7 @@ public class TValueTests
|
||||
var dt = new DateTime(2023, 1, 1, 12, 0, 0, DateTimeKind.Utc);
|
||||
var tValue = new TValue(dt.Ticks, -123.456);
|
||||
|
||||
string result = tValue.ToString();
|
||||
string result = tValue.ToString(null, CultureInfo.InvariantCulture);
|
||||
|
||||
Assert.Contains("-123.46", result, StringComparison.Ordinal);
|
||||
}
|
||||
@@ -340,9 +342,11 @@ public class TValueTests
|
||||
{
|
||||
var tv = new TValue(12345, double.NaN);
|
||||
|
||||
var hash = tv.GetHashCode();
|
||||
// Should not throw - the record struct implementation handles NaN correctly
|
||||
int hash = tv.GetHashCode();
|
||||
|
||||
Assert.True(hash != 0 || hash == 0); // Just verify it doesn't throw
|
||||
// Hash should be consistent for same NaN value
|
||||
Assert.Equal(hash, tv.GetHashCode());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -6,10 +6,11 @@ namespace QuanTAlib;
|
||||
/// <summary>
|
||||
/// A lightweight struct representing a time-value pair.
|
||||
/// Pure data type: 16 bytes (long + double).
|
||||
/// Implements ISpanFormattable for allocation-free formatting.
|
||||
/// </summary>
|
||||
[SkipLocalsInit]
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
public readonly record struct TValue(long Time, double Value)
|
||||
public readonly record struct TValue(long Time, double Value) : ISpanFormattable
|
||||
{
|
||||
public DateTime AsDateTime => new(Time, DateTimeKind.Utc);
|
||||
|
||||
@@ -37,4 +38,90 @@ public readonly record struct TValue(long Time, double Value)
|
||||
};
|
||||
return $"[{AsDateTime:yyyy-MM-dd HH:mm:ss}, {valueStr}]";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats the TValue using the specified format string.
|
||||
/// Note: Custom format and formatProvider are not supported by TValue.
|
||||
/// If a non-null/non-empty format is provided, a NotSupportedException is thrown.
|
||||
/// </summary>
|
||||
/// <param name="format">Must be null or empty; custom formats are not supported.</param>
|
||||
/// <param name="formatProvider">Ignored; TValue uses its own fixed format.</param>
|
||||
/// <returns>The string representation of this TValue.</returns>
|
||||
/// <exception cref="NotSupportedException">Thrown when a non-null/non-empty format is provided.</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public string ToString(string? format, IFormatProvider? formatProvider)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(format))
|
||||
throw new NotSupportedException($"Custom format '{format}' is not supported by TValue. Use ToString() for the default format.");
|
||||
|
||||
return ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats the TValue into the provided span without heap allocation.
|
||||
/// Format: "[yyyy-MM-dd HH:mm:ss, value]"
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider)
|
||||
{
|
||||
charsWritten = 0;
|
||||
|
||||
// Early reject for buffers too small to hold even the timestamp portion.
|
||||
// This is a heuristic check; actual buffer-overflow protection is performed
|
||||
// by the explicit length checks that guard each write operation below.
|
||||
if (destination.Length < 24)
|
||||
return false;
|
||||
|
||||
// Write opening bracket
|
||||
destination[0] = '[';
|
||||
int pos = 1;
|
||||
|
||||
// Format datetime: yyyy-MM-dd HH:mm:ss (19 chars)
|
||||
if (!AsDateTime.TryFormat(destination.Slice(pos), out int dtChars, "yyyy-MM-dd HH:mm:ss", provider))
|
||||
return false;
|
||||
pos += dtChars;
|
||||
|
||||
// Write separator
|
||||
if (pos + 2 > destination.Length)
|
||||
return false;
|
||||
destination[pos++] = ',';
|
||||
destination[pos++] = ' ';
|
||||
|
||||
// Format value
|
||||
if (double.IsPositiveInfinity(Value))
|
||||
{
|
||||
if (pos + 1 > destination.Length)
|
||||
return false;
|
||||
destination[pos++] = (char)0x221E; //
|
||||
}
|
||||
else if (double.IsNegativeInfinity(Value))
|
||||
{
|
||||
if (pos + 2 > destination.Length)
|
||||
return false;
|
||||
destination[pos++] = '-';
|
||||
destination[pos++] = (char)0x221E; // -
|
||||
}
|
||||
else if (double.IsNaN(Value))
|
||||
{
|
||||
if (pos + 3 > destination.Length)
|
||||
return false;
|
||||
destination[pos++] = 'N';
|
||||
destination[pos++] = 'a';
|
||||
destination[pos++] = 'N';
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!Value.TryFormat(destination.Slice(pos), out int valueChars, "F2", provider))
|
||||
return false;
|
||||
pos += valueChars;
|
||||
}
|
||||
|
||||
// Write closing bracket
|
||||
if (pos + 1 > destination.Length)
|
||||
return false;
|
||||
destination[pos++] = ']';
|
||||
|
||||
charsWritten = pos;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user