Skip to content

Commit bcb6cc1

Browse files
Numeric format strings: retarget net10.0, add section separators, composite formatting and TryFormat (#2171)
- 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.
1 parent d4cdcf2 commit bcb6cc1

6 files changed

Lines changed: 107 additions & 9 deletions

File tree

strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStrings/CustomFormatStrings.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,7 @@ public static class CustomFormatStrings
88
public static string DigitSeparator(double number) => number.ToString("#,###.00");
99
public static string Phone(long value) => string.Format("{0:(###) ###-####}", value);
1010
public static string PhoneInterpolated(long value) => $"{value:(###) ###-####}";
11+
public static string Accounting(double number) => number.ToString("#,##0.00;(#,##0.00);Zero");
12+
public static string BlankZero(double number) => number.ToString("0;;''");
13+
public static string EmptyThirdSection(double number) => number.ToString("#,##0.00;(#,##0.00);");
1114
}

strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStrings/StandardAndCustomFormatStrings.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
5-
<TargetFramework>net8.0</TargetFramework>
5+
<TargetFramework>net10.0</TargetFramework>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<Nullable>enable</Nullable>
88
</PropertyGroup>

strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStrings/StandardFormatsStrings.cs renamed to strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStrings/StandardFormatStrings.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,10 @@ public static class StandardFormatStrings
1111
public static string DecimalPrecision(int value) => value.ToString("D5");
1212
public static string FloatingPointPrecision(double value) => value.ToString("F2");
1313
public static string Percentage(double value) => $"{value:P2}";
14+
public static string FixedPointPrecisionDecimal(decimal value) => value.ToString("F2");
15+
public static string Aligned(double value) => string.Format("{0,12:N2}", value);
16+
public static string AlignedInterpolated(double value) => $"{value,-12:N2}";
17+
18+
public static bool TryFormatFixedPoint(double value, Span<char> destination, out int charsWritten) =>
19+
value.TryFormat(destination, out charsWritten, "F2", CultureInfo.InvariantCulture);
1420
}

strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStringsTests/CustomFormatStringsTests.cs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace StandardAndCustomNumericFormatStringsTests;
1+
namespace StandardAndCustomFormatStringsTests;
22

33
public class CustomFormatStringsTests
44
{
@@ -56,4 +56,35 @@ public void GivenANumber_WhenPhoneInterpolated_ReturnsFormattedPhoneNumber(long
5656

5757
Assert.Equal(expected, result);
5858
}
59-
}
59+
60+
[Theory]
61+
[InlineData(1234.5, "1,234.50")]
62+
[InlineData(-1234.5, "(1,234.50)")]
63+
[InlineData(0.0, "Zero")]
64+
public void GivenAValue_WhenAccounting_ThenTheSectionMatchingTheSignIsUsed(double input, string expected)
65+
{
66+
string result = CustomFormatStrings.Accounting(input);
67+
68+
Assert.Equal(expected, result);
69+
}
70+
71+
[Theory]
72+
[InlineData(1234.5, "1235")]
73+
[InlineData(-1234.5, "-1235")]
74+
[InlineData(0.0, "")]
75+
public void GivenAValue_WhenBlankZero_ThenZeroFormatsAsAnEmptyString(double input, string expected)
76+
{
77+
string result = CustomFormatStrings.BlankZero(input);
78+
79+
Assert.Equal(expected, result);
80+
}
81+
82+
[Theory]
83+
[InlineData(0.0, "0.00")]
84+
public void GivenZero_WhenTheThirdSectionIsEmpty_ThenItIsIgnoredAndTheFirstSectionIsUsed(double input, string expected)
85+
{
86+
string result = CustomFormatStrings.EmptyThirdSection(input);
87+
88+
Assert.Equal(expected, result);
89+
}
90+
}

strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStringsTests/StandardAndCustomFormatStringsTests.csproj

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net8.0</TargetFramework>
4+
<TargetFramework>net10.0</TargetFramework>
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
77

@@ -10,13 +10,13 @@
1010
</PropertyGroup>
1111

1212
<ItemGroup>
13-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
14-
<PackageReference Include="xunit" Version="2.4.2" />
15-
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
13+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
14+
<PackageReference Include="xunit" Version="2.9.3" />
15+
<PackageReference Include="xunit.runner.visualstudio" Version="4.0.0">
1616
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1717
<PrivateAssets>all</PrivateAssets>
1818
</PackageReference>
19-
<PackageReference Include="coverlet.collector" Version="6.0.0">
19+
<PackageReference Include="coverlet.collector" Version="10.0.1">
2020
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2121
<PrivateAssets>all</PrivateAssets>
2222
</PackageReference>

strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStringsTests/StandardFormatsStringsTests.cs renamed to strings-csharp/StandardAndCustomFormatStrings/StandardAndCustomFormatStringsTests/StandardFormatStringsTests.cs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,62 @@ public void GivenANumber_WhenPercentage_ReturnsPercentage(double input, string e
6464

6565
Assert.Contains(expected, result);
6666
}
67-
}
67+
68+
[Theory]
69+
[InlineData(1.005, "1.00")]
70+
[InlineData(0.125, "0.12")]
71+
public void GivenAnApparentMidpoint_WhenFloatingPointPrecision_ThenTheStoredBinaryValueDecides(double input, string expected)
72+
{
73+
string result = StandardFormatStrings.FloatingPointPrecision(input);
74+
75+
Assert.Equal(expected, result);
76+
}
77+
78+
[Fact]
79+
public void GivenAMidpoint_WhenFixedPointPrecisionDecimal_ThenItRoundsAwayFromZero()
80+
{
81+
Assert.Equal("1.01", StandardFormatStrings.FixedPointPrecisionDecimal(1.005m));
82+
Assert.Equal("0.13", StandardFormatStrings.FixedPointPrecisionDecimal(0.125m));
83+
}
84+
85+
[Theory]
86+
[InlineData(1652.5899, " 1,652.59")]
87+
public void GivenAValue_WhenAligned_ThenItIsRightAlignedInATwelveCharacterField(double input, string expected)
88+
{
89+
string result = StandardFormatStrings.Aligned(input);
90+
91+
Assert.Equal(expected, result);
92+
}
93+
94+
[Theory]
95+
[InlineData(1652.5899, "1,652.59 ")]
96+
public void GivenAValue_WhenAlignedInterpolated_ThenANegativeWidthLeftAlignsIt(double input, string expected)
97+
{
98+
string result = StandardFormatStrings.AlignedInterpolated(input);
99+
100+
Assert.Equal(expected, result);
101+
}
102+
103+
[Fact]
104+
public void GivenABigEnoughBuffer_WhenTryFormatFixedPoint_ThenItWritesTheValueAndReturnsTrue()
105+
{
106+
Span<char> buffer = stackalloc char[16];
107+
108+
bool succeeded = StandardFormatStrings.TryFormatFixedPoint(1652.5899, buffer, out int charsWritten);
109+
110+
Assert.True(succeeded);
111+
Assert.Equal(7, charsWritten);
112+
Assert.Equal("1652.59", buffer[..charsWritten].ToString());
113+
}
114+
115+
[Fact]
116+
public void GivenATooSmallBuffer_WhenTryFormatFixedPoint_ThenItWritesNothingAndReturnsFalse()
117+
{
118+
Span<char> buffer = stackalloc char[2];
119+
120+
bool succeeded = StandardFormatStrings.TryFormatFixedPoint(1652.5899, buffer, out int charsWritten);
121+
122+
Assert.False(succeeded);
123+
Assert.Equal(0, charsWritten);
124+
}
125+
}

0 commit comments

Comments
 (0)