code reviews

This commit is contained in:
Miha Kralj
2026-01-18 22:23:50 -08:00
parent 86fe32a682
commit 4673f48a70
40 changed files with 1155 additions and 309 deletions
+10 -6
View File
@@ -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]
+89 -2
View File
@@ -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;
}
}