feat: add float/double support for WriteNoAlloc and WriteLineNoAlloc

This commit is contained in:
Oliver Booth 2023-04-11 22:27:31 +01:00
parent f13907a4d0
commit b8a63c0d5c
No known key found for this signature in database
GPG Key ID: 20BEB9DC87961025
6 changed files with 610 additions and 0 deletions

View File

@ -0,0 +1,131 @@
using System.Globalization;
using System.Text;
using NUnit.Framework;
using X10D.IO;
namespace X10D.Tests.IO;
public partial class TextWriterTests
{
[Test]
public void WriteNoAlloc_ShouldThrowArgumentNullException_GivenDouble_AndNullWriter()
{
TextWriter writer = null!;
Assert.Throws<ArgumentNullException>(() => writer.WriteNoAlloc(420.0));
Assert.Throws<ArgumentNullException>(() => writer.WriteNoAlloc(420.0, "N0"));
Assert.Throws<ArgumentNullException>(() => writer.WriteNoAlloc(420.0, "N0", null));
}
[Test]
public void WriteNoAlloc_ShouldThrowObjectDisposedException_GivenDouble_AndDisposedStream()
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream, Encoding.UTF8);
writer.Dispose();
stream.Dispose();
Assert.Throws<ObjectDisposedException>(() => writer.WriteNoAlloc(420.0));
Assert.Throws<ObjectDisposedException>(() => writer.WriteNoAlloc(420.0, "N0"));
Assert.Throws<ObjectDisposedException>(() => writer.WriteNoAlloc(420.0, "N0", null));
}
[Test]
public void WriteLineNoAlloc_ShouldThrowArgumentNullException_GivenDouble_AndNullWriter()
{
TextWriter writer = null!;
Assert.Throws<ArgumentNullException>(() => writer.WriteLineNoAlloc(420.0));
Assert.Throws<ArgumentNullException>(() => writer.WriteLineNoAlloc(420.0, "N0"));
Assert.Throws<ArgumentNullException>(() => writer.WriteLineNoAlloc(420.0, "N0", null));
}
[Test]
public void WriteLineNoAlloc_ShouldThrowObjectDisposedException_GivenDouble_AndDisposedStream()
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream, Encoding.UTF8);
writer.Dispose();
stream.Dispose();
Assert.Throws<ObjectDisposedException>(() => writer.WriteLineNoAlloc(420.0));
Assert.Throws<ObjectDisposedException>(() => writer.WriteLineNoAlloc(420.0, "N0"));
Assert.Throws<ObjectDisposedException>(() => writer.WriteLineNoAlloc(420.0, "N0", null));
}
[Test]
public void WriteNoAlloc_ShouldWriteTextValue_GivenDouble()
{
Assert.That(_stream.Length, Is.Zero);
_writer.WriteNoAlloc(420.0);
_writer.Flush();
string actual = Encoding.UTF8.GetString(_stream.ToArray());
const string expected = "420";
Assert.That(actual, Is.EqualTo(expected));
}
[Test]
public void WriteNoAlloc_ShouldWriteTextValue_GivenDouble_AndFormatString()
{
Assert.That(_stream.Length, Is.Zero);
_writer.WriteNoAlloc(420.0, "N0");
_writer.Flush();
string actual = Encoding.UTF8.GetString(_stream.ToArray());
const string expected = "420";
Assert.That(actual, Is.EqualTo(expected));
}
[Test]
public void WriteNoAlloc_ShouldWriteTextValue_GivenDouble_AndFormatString_AndCultureInfo()
{
Assert.That(_stream.Length, Is.Zero);
_writer.WriteNoAlloc(420.0, "N0", CultureInfo.CurrentCulture);
_writer.Flush();
string actual = Encoding.UTF8.GetString(_stream.ToArray());
const string expected = "420";
Assert.That(actual, Is.EqualTo(expected));
}
[Test]
public void WriteLineNoAlloc_ShouldWriteTextValue_GivenDouble()
{
Assert.That(_stream.Length, Is.Zero);
_writer.WriteLineNoAlloc(420.0);
_writer.Flush();
string actual = Encoding.UTF8.GetString(_stream.ToArray());
var expected = $"420{Environment.NewLine}";
Assert.That(actual, Is.EqualTo(expected));
}
[Test]
public void WriteLineNoAlloc_ShouldWriteTextValue_GivenDouble_AndFormatString()
{
Assert.That(_stream.Length, Is.Zero);
_writer.WriteLineNoAlloc(420.0, "N0");
_writer.Flush();
string actual = Encoding.UTF8.GetString(_stream.ToArray());
var expected = $"420{Environment.NewLine}";
Assert.That(actual, Is.EqualTo(expected));
}
[Test]
public void WriteLineNoAlloc_ShouldWriteTextValue_GivenDouble_AndFormatString_AndCultureInfo()
{
Assert.That(_stream.Length, Is.Zero);
_writer.WriteLineNoAlloc(420.0, "N0", CultureInfo.CurrentCulture);
_writer.Flush();
string actual = Encoding.UTF8.GetString(_stream.ToArray());
var expected = $"420{Environment.NewLine}";
Assert.That(actual, Is.EqualTo(expected));
}
}

View File

@ -0,0 +1,131 @@
using System.Globalization;
using System.Text;
using NUnit.Framework;
using X10D.IO;
namespace X10D.Tests.IO;
public partial class TextWriterTests
{
[Test]
public void WriteNoAlloc_ShouldThrowArgumentNullException_GivenSingle_AndNullWriter()
{
TextWriter writer = null!;
Assert.Throws<ArgumentNullException>(() => writer.WriteNoAlloc(420.0f));
Assert.Throws<ArgumentNullException>(() => writer.WriteNoAlloc(420.0f, "N0"));
Assert.Throws<ArgumentNullException>(() => writer.WriteNoAlloc(420.0f, "N0", null));
}
[Test]
public void WriteNoAlloc_ShouldThrowObjectDisposedException_GivenSingle_AndDisposedStream()
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream, Encoding.UTF8);
writer.Dispose();
stream.Dispose();
Assert.Throws<ObjectDisposedException>(() => writer.WriteNoAlloc(420.0f));
Assert.Throws<ObjectDisposedException>(() => writer.WriteNoAlloc(420.0f, "N0"));
Assert.Throws<ObjectDisposedException>(() => writer.WriteNoAlloc(420.0f, "N0", null));
}
[Test]
public void WriteLineNoAlloc_ShouldThrowArgumentNullException_GivenSingle_AndNullWriter()
{
TextWriter writer = null!;
Assert.Throws<ArgumentNullException>(() => writer.WriteLineNoAlloc(420.0f));
Assert.Throws<ArgumentNullException>(() => writer.WriteLineNoAlloc(420.0f, "N0"));
Assert.Throws<ArgumentNullException>(() => writer.WriteLineNoAlloc(420.0f, "N0", null));
}
[Test]
public void WriteLineNoAlloc_ShouldThrowObjectDisposedException_GivenSingle_AndDisposedStream()
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream, Encoding.UTF8);
writer.Dispose();
stream.Dispose();
Assert.Throws<ObjectDisposedException>(() => writer.WriteLineNoAlloc(420.0f));
Assert.Throws<ObjectDisposedException>(() => writer.WriteLineNoAlloc(420.0f, "N0"));
Assert.Throws<ObjectDisposedException>(() => writer.WriteLineNoAlloc(420.0f, "N0", null));
}
[Test]
public void WriteNoAlloc_ShouldWriteTextValue_GivenSingle()
{
Assert.That(_stream.Length, Is.Zero);
_writer.WriteNoAlloc(420.0f);
_writer.Flush();
string actual = Encoding.UTF8.GetString(_stream.ToArray());
const string expected = "420";
Assert.That(actual, Is.EqualTo(expected));
}
[Test]
public void WriteNoAlloc_ShouldWriteTextValue_GivenSingle_AndFormatString()
{
Assert.That(_stream.Length, Is.Zero);
_writer.WriteNoAlloc(420.0f, "N0");
_writer.Flush();
string actual = Encoding.UTF8.GetString(_stream.ToArray());
const string expected = "420";
Assert.That(actual, Is.EqualTo(expected));
}
[Test]
public void WriteNoAlloc_ShouldWriteTextValue_GivenSingle_AndFormatString_AndCultureInfo()
{
Assert.That(_stream.Length, Is.Zero);
_writer.WriteNoAlloc(420.0f, "N0", CultureInfo.CurrentCulture);
_writer.Flush();
string actual = Encoding.UTF8.GetString(_stream.ToArray());
const string expected = "420";
Assert.That(actual, Is.EqualTo(expected));
}
[Test]
public void WriteLineNoAlloc_ShouldWriteTextValue_GivenSingle()
{
Assert.That(_stream.Length, Is.Zero);
_writer.WriteLineNoAlloc(420.0f);
_writer.Flush();
string actual = Encoding.UTF8.GetString(_stream.ToArray());
var expected = $"420{Environment.NewLine}";
Assert.That(actual, Is.EqualTo(expected));
}
[Test]
public void WriteLineNoAlloc_ShouldWriteTextValue_GivenSingle_AndFormatString()
{
Assert.That(_stream.Length, Is.Zero);
_writer.WriteLineNoAlloc(420.0f, "N0");
_writer.Flush();
string actual = Encoding.UTF8.GetString(_stream.ToArray());
var expected = $"420{Environment.NewLine}";
Assert.That(actual, Is.EqualTo(expected));
}
[Test]
public void WriteLineNoAlloc_ShouldWriteTextValue_GivenSingle_AndFormatString_AndCultureInfo()
{
Assert.That(_stream.Length, Is.Zero);
_writer.WriteLineNoAlloc(420.0f, "N0", CultureInfo.CurrentCulture);
_writer.Flush();
string actual = Encoding.UTF8.GetString(_stream.ToArray());
var expected = $"420{Environment.NewLine}";
Assert.That(actual, Is.EqualTo(expected));
}
}

View File

@ -0,0 +1,83 @@
using System.Globalization;
namespace X10D.IO;
public static partial class TextWriterExtensions
{
/// <summary>
/// Writes the text representation of an 8-byte floating-point value to the text stream, followed by a line terminator,
/// without allocating a string.
/// </summary>
/// <param name="writer">The <see cref="TextWriter" /> to write to.</param>
/// <param name="value">The 8-byte floating-point value to write.</param>
/// <remarks>This method may still allocate if the integer is too large to fit in a stack-allocated buffer.</remarks>
/// <exception cref="ArgumentNullException"><paramref name="writer" /> is <see langword="null" />.</exception>
/// <exception cref="ObjectDisposedException">The <see cref="TextWriter" /> is closed.</exception>
/// <exception cref="IOException">An I/O error occurs.</exception>
public static void WriteLineNoAlloc(this TextWriter writer, double value)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(writer);
#else
if (writer is null)
{
throw new ArgumentNullException(nameof(writer));
}
#endif
writer.WriteLineNoAlloc(value, "N0".AsSpan(), CultureInfo.CurrentCulture);
}
/// <summary>
/// Writes the text representation of an 8-byte floating-point value to the text stream, followed by a line terminator,
/// without allocating a string.
/// </summary>
/// <param name="writer">The <see cref="TextWriter" /> to write to.</param>
/// <param name="value">The 8-byte floating-point value to write.</param>
/// <param name="format">A standard or custom numeric format string.</param>
/// <remarks>This method may still allocate if the integer is too large to fit in a stack-allocated buffer.</remarks>
/// <exception cref="ArgumentNullException"><paramref name="writer" /> is <see langword="null" />.</exception>
/// <exception cref="ObjectDisposedException">The <see cref="TextWriter" /> is closed.</exception>
/// <exception cref="IOException">An I/O error occurs.</exception>
public static void WriteLineNoAlloc(this TextWriter writer, double value, ReadOnlySpan<char> format)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(writer);
#else
if (writer is null)
{
throw new ArgumentNullException(nameof(writer));
}
#endif
WriteLineNoAlloc(writer, value, format, CultureInfo.CurrentCulture);
}
/// <summary>
/// Writes the text representation of an 8-byte floating-point value to the text stream, followed by a line terminator,
/// without allocating a string.
/// </summary>
/// <param name="writer">The <see cref="TextWriter" /> to write to.</param>
/// <param name="value">The 8-byte floating-point value to write.</param>
/// <param name="format">A standard or custom numeric format string.</param>
/// <param name="formatProvider">An object that supplies culture-specific formatting information.</param>
/// <remarks>This method may still allocate if the integer is too large to fit in a stack-allocated buffer.</remarks>
/// <exception cref="ArgumentNullException"><paramref name="writer" /> is <see langword="null" />.</exception>
/// <exception cref="ObjectDisposedException">The <see cref="TextWriter" /> is closed.</exception>
/// <exception cref="IOException">An I/O error occurs.</exception>
public static void WriteLineNoAlloc(this TextWriter writer, double value, ReadOnlySpan<char> format,
IFormatProvider? formatProvider)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(writer);
#else
if (writer is null)
{
throw new ArgumentNullException(nameof(writer));
}
#endif
writer.WriteNoAlloc(value, format, formatProvider);
writer.WriteLine();
}
}

View File

@ -0,0 +1,83 @@
using System.Globalization;
namespace X10D.IO;
public static partial class TextWriterExtensions
{
/// <summary>
/// Writes the text representation of a 4-byte floating-point value to the text stream, followed by a line terminator,
/// without allocating a string.
/// </summary>
/// <param name="writer">The <see cref="TextWriter" /> to write to.</param>
/// <param name="value">The 4-byte floating-point value to write.</param>
/// <remarks>This method may still allocate if the integer is too large to fit in a stack-allocated buffer.</remarks>
/// <exception cref="ArgumentNullException"><paramref name="writer" /> is <see langword="null" />.</exception>
/// <exception cref="ObjectDisposedException">The <see cref="TextWriter" /> is closed.</exception>
/// <exception cref="IOException">An I/O error occurs.</exception>
public static void WriteLineNoAlloc(this TextWriter writer, float value)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(writer);
#else
if (writer is null)
{
throw new ArgumentNullException(nameof(writer));
}
#endif
writer.WriteLineNoAlloc(value, "N0".AsSpan(), CultureInfo.CurrentCulture);
}
/// <summary>
/// Writes the text representation of a 4-byte floating-point value to the text stream, followed by a line terminator,
/// without allocating a string.
/// </summary>
/// <param name="writer">The <see cref="TextWriter" /> to write to.</param>
/// <param name="value">The 4-byte floating-point value to write.</param>
/// <param name="format">A standard or custom numeric format string.</param>
/// <remarks>This method may still allocate if the integer is too large to fit in a stack-allocated buffer.</remarks>
/// <exception cref="ArgumentNullException"><paramref name="writer" /> is <see langword="null" />.</exception>
/// <exception cref="ObjectDisposedException">The <see cref="TextWriter" /> is closed.</exception>
/// <exception cref="IOException">An I/O error occurs.</exception>
public static void WriteLineNoAlloc(this TextWriter writer, float value, ReadOnlySpan<char> format)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(writer);
#else
if (writer is null)
{
throw new ArgumentNullException(nameof(writer));
}
#endif
WriteLineNoAlloc(writer, value, format, CultureInfo.CurrentCulture);
}
/// <summary>
/// Writes the text representation of a 4-byte floating-point value to the text stream, followed by a line terminator,
/// without allocating a string.
/// </summary>
/// <param name="writer">The <see cref="TextWriter" /> to write to.</param>
/// <param name="value">The 4-byte floating-point value to write.</param>
/// <param name="format">A standard or custom numeric format string.</param>
/// <param name="formatProvider">An object that supplies culture-specific formatting information.</param>
/// <remarks>This method may still allocate if the integer is too large to fit in a stack-allocated buffer.</remarks>
/// <exception cref="ArgumentNullException"><paramref name="writer" /> is <see langword="null" />.</exception>
/// <exception cref="ObjectDisposedException">The <see cref="TextWriter" /> is closed.</exception>
/// <exception cref="IOException">An I/O error occurs.</exception>
public static void WriteLineNoAlloc(this TextWriter writer, float value, ReadOnlySpan<char> format,
IFormatProvider? formatProvider)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(writer);
#else
if (writer is null)
{
throw new ArgumentNullException(nameof(writer));
}
#endif
writer.WriteNoAlloc(value, format, formatProvider);
writer.WriteLine();
}
}

View File

@ -0,0 +1,91 @@
using System.Globalization;
namespace X10D.IO;
public static partial class TextWriterExtensions
{
/// <summary>
/// Writes the text representation of an 8-byte floating-point value to the text stream, without allocating a string.
/// </summary>
/// <param name="writer">The <see cref="TextWriter" /> to write to.</param>
/// <param name="value">The 8-byte floating-point value to write.</param>
/// <remarks>This method may still allocate if the integer is too large to fit in a stack-allocated buffer.</remarks>
/// <exception cref="ArgumentNullException"><paramref name="writer" /> is <see langword="null" />.</exception>
/// <exception cref="ObjectDisposedException">The <see cref="TextWriter" /> is closed.</exception>
/// <exception cref="IOException">An I/O error occurs.</exception>
public static void WriteNoAlloc(this TextWriter writer, double value)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(writer);
#else
if (writer is null)
{
throw new ArgumentNullException(nameof(writer));
}
#endif
WriteNoAlloc(writer, value, "N0".AsSpan(), CultureInfo.CurrentCulture);
}
/// <summary>
/// Writes the text representation of an 8-byte floating-point value to the text stream, without allocating a string.
/// </summary>
/// <param name="writer">The <see cref="TextWriter" /> to write to.</param>
/// <param name="value">The 8-byte floating-point value to write.</param>
/// <param name="format">A standard or custom numeric format string.</param>
/// <remarks>This method may still allocate if the integer is too large to fit in a stack-allocated buffer.</remarks>
/// <exception cref="ArgumentNullException"><paramref name="writer" /> is <see langword="null" />.</exception>
/// <exception cref="ObjectDisposedException">The <see cref="TextWriter" /> is closed.</exception>
/// <exception cref="IOException">An I/O error occurs.</exception>
public static void WriteNoAlloc(this TextWriter writer, double value, ReadOnlySpan<char> format)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(writer);
#else
if (writer is null)
{
throw new ArgumentNullException(nameof(writer));
}
#endif
WriteNoAlloc(writer, value, format, CultureInfo.CurrentCulture);
}
/// <summary>
/// Writes the text representation of an 8-byte floating-point value to the text stream, without allocating a string.
/// </summary>
/// <param name="writer">The <see cref="TextWriter" /> to write to.</param>
/// <param name="value">The 8-byte floating-point value to write.</param>
/// <param name="format">A standard or custom numeric format string.</param>
/// <param name="formatProvider">An object that supplies culture-specific formatting information.</param>
/// <remarks>This method may still allocate if the integer is too large to fit in a stack-allocated buffer.</remarks>
/// <exception cref="ArgumentNullException"><paramref name="writer" /> is <see langword="null" />.</exception>
/// <exception cref="ObjectDisposedException">The <see cref="TextWriter" /> is closed.</exception>
/// <exception cref="IOException">An I/O error occurs.</exception>
public static void WriteNoAlloc(this TextWriter writer, double value, ReadOnlySpan<char> format,
IFormatProvider? formatProvider)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(writer);
#else
if (writer is null)
{
throw new ArgumentNullException(nameof(writer));
}
#endif
Span<char> buffer = stackalloc char[100];
if (value.TryFormat(buffer, out int charsWritten, format, formatProvider))
{
Span<char> truncated = buffer[..charsWritten];
for (var index = 0; index < truncated.Length; index++)
{
writer.Write(truncated[index]);
}
}
else
{
writer.Write(value.ToString(format.ToString(), formatProvider));
}
}
}

View File

@ -0,0 +1,91 @@
using System.Globalization;
namespace X10D.IO;
public static partial class TextWriterExtensions
{
/// <summary>
/// Writes the text representation of a 4-byte floating-point value to the text stream, without allocating a string.
/// </summary>
/// <param name="writer">The <see cref="TextWriter" /> to write to.</param>
/// <param name="value">The 4-byte floating-point value to write.</param>
/// <remarks>This method may still allocate if the integer is too large to fit in a stack-allocated buffer.</remarks>
/// <exception cref="ArgumentNullException"><paramref name="writer" /> is <see langword="null" />.</exception>
/// <exception cref="ObjectDisposedException">The <see cref="TextWriter" /> is closed.</exception>
/// <exception cref="IOException">An I/O error occurs.</exception>
public static void WriteNoAlloc(this TextWriter writer, float value)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(writer);
#else
if (writer is null)
{
throw new ArgumentNullException(nameof(writer));
}
#endif
WriteNoAlloc(writer, value, "N0".AsSpan(), CultureInfo.CurrentCulture);
}
/// <summary>
/// Writes the text representation of a 4-byte floating-point value to the text stream, without allocating a string.
/// </summary>
/// <param name="writer">The <see cref="TextWriter" /> to write to.</param>
/// <param name="value">The 4-byte floating-point value to write.</param>
/// <param name="format">A standard or custom numeric format string.</param>
/// <remarks>This method may still allocate if the integer is too large to fit in a stack-allocated buffer.</remarks>
/// <exception cref="ArgumentNullException"><paramref name="writer" /> is <see langword="null" />.</exception>
/// <exception cref="ObjectDisposedException">The <see cref="TextWriter" /> is closed.</exception>
/// <exception cref="IOException">An I/O error occurs.</exception>
public static void WriteNoAlloc(this TextWriter writer, float value, ReadOnlySpan<char> format)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(writer);
#else
if (writer is null)
{
throw new ArgumentNullException(nameof(writer));
}
#endif
WriteNoAlloc(writer, value, format, CultureInfo.CurrentCulture);
}
/// <summary>
/// Writes the text representation of a 4-byte floating-point value to the text stream, without allocating a string.
/// </summary>
/// <param name="writer">The <see cref="TextWriter" /> to write to.</param>
/// <param name="value">The 4-byte floating-point value to write.</param>
/// <param name="format">A standard or custom numeric format string.</param>
/// <param name="formatProvider">An object that supplies culture-specific formatting information.</param>
/// <remarks>This method may still allocate if the integer is too large to fit in a stack-allocated buffer.</remarks>
/// <exception cref="ArgumentNullException"><paramref name="writer" /> is <see langword="null" />.</exception>
/// <exception cref="ObjectDisposedException">The <see cref="TextWriter" /> is closed.</exception>
/// <exception cref="IOException">An I/O error occurs.</exception>
public static void WriteNoAlloc(this TextWriter writer, float value, ReadOnlySpan<char> format,
IFormatProvider? formatProvider)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(writer);
#else
if (writer is null)
{
throw new ArgumentNullException(nameof(writer));
}
#endif
Span<char> buffer = stackalloc char[100];
if (value.TryFormat(buffer, out int charsWritten, format, formatProvider))
{
Span<char> truncated = buffer[..charsWritten];
for (var index = 0; index < truncated.Length; index++)
{
writer.Write(truncated[index]);
}
}
else
{
writer.Write(value.ToString(format.ToString(), formatProvider));
}
}
}