From a47fe70de7c1f8c87d9ab92e125883b4e41644ba Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Sun, 30 Aug 2026 21:45:46 +0200 Subject: [PATCH] Numeric format strings: retarget net10.0, add section separators, composite formatting and TryFormat - Retarget both projects from net8.0 to net10.0. - Lift test packages: Microsoft.NET.Test.Sdk 18.9.0, xunit 2.9.3, xunit.runner.visualstudio 4.0.0, coverlet.collector 10.0.1. - CustomFormatStrings: add Accounting, BlankZero and EmptyThirdSection so the semicolon section separator the article now teaches is runnable, including the counter-intuitive case where an empty third section is ignored. - StandardFormatStrings: add FixedPointPrecisionDecimal, Aligned, AlignedInterpolated and TryFormatFixedPoint. - Tests pin the rounding difference between double and decimal under "F2", the composite format item's alignment component, and both TryFormat paths (big enough buffer and too small a buffer). - Tidy: StandardFormatsStrings.cs renamed to StandardFormatStrings.cs (and its test file likewise) to match the class name, and CustomFormatStringsTests moved to the same namespace as the rest of the test project. --- .../CustomFormatStrings.cs | 3 + .../StandardAndCustomFormatStrings.csproj | 2 +- ...atsStrings.cs => StandardFormatStrings.cs} | 6 ++ .../CustomFormatStringsTests.cs | 35 ++++++++++- ...StandardAndCustomFormatStringsTests.csproj | 10 ++-- ...Tests.cs => StandardFormatStringsTests.cs} | 60 ++++++++++++++++++- 6 files changed, 107 insertions(+), 9 deletions(-) rename strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStrings/{StandardFormatsStrings.cs => StandardFormatStrings.cs} (60%) rename strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStringsTests/{StandardFormatsStringsTests.cs => StandardFormatStringsTests.cs} (50%) diff --git a/strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStrings/CustomFormatStrings.cs b/strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStrings/CustomFormatStrings.cs index 0b78e057a9..dca24743c3 100644 --- a/strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStrings/CustomFormatStrings.cs +++ b/strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStrings/CustomFormatStrings.cs @@ -8,4 +8,7 @@ public static class CustomFormatStrings public static string DigitSeparator(double number) => number.ToString("#,###.00"); public static string Phone(long value) => string.Format("{0:(###) ###-####}", value); public static string PhoneInterpolated(long value) => $"{value:(###) ###-####}"; + public static string Accounting(double number) => number.ToString("#,##0.00;(#,##0.00);Zero"); + public static string BlankZero(double number) => number.ToString("0;;''"); + public static string EmptyThirdSection(double number) => number.ToString("#,##0.00;(#,##0.00);"); } \ No newline at end of file diff --git a/strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStrings/StandardAndCustomFormatStrings.csproj b/strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStrings/StandardAndCustomFormatStrings.csproj index 2150e3797b..ed9781c223 100644 --- a/strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStrings/StandardAndCustomFormatStrings.csproj +++ b/strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStrings/StandardAndCustomFormatStrings.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 enable enable diff --git a/strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStrings/StandardFormatsStrings.cs b/strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStrings/StandardFormatStrings.cs similarity index 60% rename from strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStrings/StandardFormatsStrings.cs rename to strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStrings/StandardFormatStrings.cs index fa9ee6811f..4c1a38b9ef 100644 --- a/strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStrings/StandardFormatsStrings.cs +++ b/strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStrings/StandardFormatStrings.cs @@ -11,4 +11,10 @@ public static class StandardFormatStrings public static string DecimalPrecision(int value) => value.ToString("D5"); public static string FloatingPointPrecision(double value) => value.ToString("F2"); public static string Percentage(double value) => $"{value:P2}"; + public static string FixedPointPrecisionDecimal(decimal value) => value.ToString("F2"); + public static string Aligned(double value) => string.Format("{0,12:N2}", value); + public static string AlignedInterpolated(double value) => $"{value,-12:N2}"; + + public static bool TryFormatFixedPoint(double value, Span destination, out int charsWritten) => + value.TryFormat(destination, out charsWritten, "F2", CultureInfo.InvariantCulture); } \ No newline at end of file diff --git a/strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStringsTests/CustomFormatStringsTests.cs b/strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStringsTests/CustomFormatStringsTests.cs index a672cf105a..20b27ceb38 100644 --- a/strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStringsTests/CustomFormatStringsTests.cs +++ b/strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStringsTests/CustomFormatStringsTests.cs @@ -1,4 +1,4 @@ -namespace StandardAndCustomNumericFormatStringsTests; +namespace StandardAndCustomFormatStringsTests; public class CustomFormatStringsTests { @@ -56,4 +56,35 @@ public void GivenANumber_WhenPhoneInterpolated_ReturnsFormattedPhoneNumber(long Assert.Equal(expected, result); } -} \ No newline at end of file + + [Theory] + [InlineData(1234.5, "1,234.50")] + [InlineData(-1234.5, "(1,234.50)")] + [InlineData(0.0, "Zero")] + public void GivenAValue_WhenAccounting_ThenTheSectionMatchingTheSignIsUsed(double input, string expected) + { + string result = CustomFormatStrings.Accounting(input); + + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(1234.5, "1235")] + [InlineData(-1234.5, "-1235")] + [InlineData(0.0, "")] + public void GivenAValue_WhenBlankZero_ThenZeroFormatsAsAnEmptyString(double input, string expected) + { + string result = CustomFormatStrings.BlankZero(input); + + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(0.0, "0.00")] + public void GivenZero_WhenTheThirdSectionIsEmpty_ThenItIsIgnoredAndTheFirstSectionIsUsed(double input, string expected) + { + string result = CustomFormatStrings.EmptyThirdSection(input); + + Assert.Equal(expected, result); + } +} diff --git a/strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStringsTests/StandardAndCustomFormatStringsTests.csproj b/strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStringsTests/StandardAndCustomFormatStringsTests.csproj index 23191912ae..57db3479bf 100644 --- a/strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStringsTests/StandardAndCustomFormatStringsTests.csproj +++ b/strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStringsTests/StandardAndCustomFormatStringsTests.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable @@ -10,13 +10,13 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStringsTests/StandardFormatsStringsTests.cs b/strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStringsTests/StandardFormatStringsTests.cs similarity index 50% rename from strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStringsTests/StandardFormatsStringsTests.cs rename to strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStringsTests/StandardFormatStringsTests.cs index 7878c12502..b312e6d1a2 100644 --- a/strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStringsTests/StandardFormatsStringsTests.cs +++ b/strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStringsTests/StandardFormatStringsTests.cs @@ -64,4 +64,62 @@ public void GivenANumber_WhenPercentage_ReturnsPercentage(double input, string e Assert.Contains(expected, result); } -} \ No newline at end of file + + [Theory] + [InlineData(1.005, "1.00")] + [InlineData(0.125, "0.12")] + public void GivenAnApparentMidpoint_WhenFloatingPointPrecision_ThenTheStoredBinaryValueDecides(double input, string expected) + { + string result = StandardFormatStrings.FloatingPointPrecision(input); + + Assert.Equal(expected, result); + } + + [Fact] + public void GivenAMidpoint_WhenFixedPointPrecisionDecimal_ThenItRoundsAwayFromZero() + { + Assert.Equal("1.01", StandardFormatStrings.FixedPointPrecisionDecimal(1.005m)); + Assert.Equal("0.13", StandardFormatStrings.FixedPointPrecisionDecimal(0.125m)); + } + + [Theory] + [InlineData(1652.5899, " 1,652.59")] + public void GivenAValue_WhenAligned_ThenItIsRightAlignedInATwelveCharacterField(double input, string expected) + { + string result = StandardFormatStrings.Aligned(input); + + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(1652.5899, "1,652.59 ")] + public void GivenAValue_WhenAlignedInterpolated_ThenANegativeWidthLeftAlignsIt(double input, string expected) + { + string result = StandardFormatStrings.AlignedInterpolated(input); + + Assert.Equal(expected, result); + } + + [Fact] + public void GivenABigEnoughBuffer_WhenTryFormatFixedPoint_ThenItWritesTheValueAndReturnsTrue() + { + Span buffer = stackalloc char[16]; + + bool succeeded = StandardFormatStrings.TryFormatFixedPoint(1652.5899, buffer, out int charsWritten); + + Assert.True(succeeded); + Assert.Equal(7, charsWritten); + Assert.Equal("1652.59", buffer[..charsWritten].ToString()); + } + + [Fact] + public void GivenATooSmallBuffer_WhenTryFormatFixedPoint_ThenItWritesNothingAndReturnsFalse() + { + Span buffer = stackalloc char[2]; + + bool succeeded = StandardFormatStrings.TryFormatFixedPoint(1652.5899, buffer, out int charsWritten); + + Assert.False(succeeded); + Assert.Equal(0, charsWritten); + } +}