✅ Am I using NumberFormatInfo properly?

I have a list of ca. 400 numbers which I want to convert to percentage strings as part of an endpoint. I know I will only ever want two decimal places or three decimal places. I was wondering about the impact of allocating a new NumberFormatInfo each time so I whipped up some code:
[Benchmark]
public static string NoCache2()
{
return ToPercentageString(0.0433m, 2);
}
[Benchmark]
public static string NoCache3()
{
return ToPercentageString(0.0433m, 3);
}
[Benchmark]
public static string Cache2()
{
return ToPercentageString2Dp(0.0433m);
}
[Benchmark]
public static string Cache3()
{
return ToPercentageString3Dp(0.0433m);
}

public static string ToPercentageString(decimal d, int decimalPlaces)
=> d.ToString(
"P",
new NumberFormatInfo()
{
PercentDecimalDigits = decimalPlaces,
PercentPositivePattern = 1
}
);

public static string ToPercentageString2Dp(decimal d)
=> d.ToString("P", TwoDpFormat);

public static string ToPercentageString3Dp(decimal d)
=> d.ToString("P", ThreeDpFormat);

private static readonly NumberFormatInfo TwoDpFormat = new NumberFormatInfo()
{
PercentDecimalDigits = 2,
PercentPositivePattern = 1
};

private static readonly NumberFormatInfo ThreeDpFormat = new NumberFormatInfo()
{
PercentDecimalDigits = 3,
PercentPositivePattern = 1
};
[Benchmark]
public static string NoCache2()
{
return ToPercentageString(0.0433m, 2);
}
[Benchmark]
public static string NoCache3()
{
return ToPercentageString(0.0433m, 3);
}
[Benchmark]
public static string Cache2()
{
return ToPercentageString2Dp(0.0433m);
}
[Benchmark]
public static string Cache3()
{
return ToPercentageString3Dp(0.0433m);
}

public static string ToPercentageString(decimal d, int decimalPlaces)
=> d.ToString(
"P",
new NumberFormatInfo()
{
PercentDecimalDigits = decimalPlaces,
PercentPositivePattern = 1
}
);

public static string ToPercentageString2Dp(decimal d)
=> d.ToString("P", TwoDpFormat);

public static string ToPercentageString3Dp(decimal d)
=> d.ToString("P", ThreeDpFormat);

private static readonly NumberFormatInfo TwoDpFormat = new NumberFormatInfo()
{
PercentDecimalDigits = 2,
PercentPositivePattern = 1
};

private static readonly NumberFormatInfo ThreeDpFormat = new NumberFormatInfo()
{
PercentDecimalDigits = 3,
PercentPositivePattern = 1
};

The results indicate caching is better but the end result after all the numbers in terms of allocs or nanoseconds saved seems unlikely to be significant (probably 217kB memory saved, or 12 microseconds faster). Is this premature optimization? OR is there a more idiomatic way to use NumberFormatInfo that I'm missing here?
4 Replies
dreadfullydistinct
| Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
|--------- |---------:|---------:|---------:|-------:|-------:|----------:|
| NoCache2 | 67.87 ns | 0.825 ns | 0.771 ns | 0.0324 | 0.0001 | 544 B |
| NoCache3 | 72.17 ns | 0.917 ns | 0.858 ns | 0.0329 | 0.0001 | 552 B |
| Cache2 | 38.18 ns | 0.343 ns | 0.304 ns | 0.0019 | 0.0001 | 32 B |
| Cache3 | 40.21 ns | 0.170 ns | 0.151 ns | 0.0024 | 0.0001 | 40 B |
| Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
|--------- |---------:|---------:|---------:|-------:|-------:|----------:|
| NoCache2 | 67.87 ns | 0.825 ns | 0.771 ns | 0.0324 | 0.0001 | 544 B |
| NoCache3 | 72.17 ns | 0.917 ns | 0.858 ns | 0.0329 | 0.0001 | 552 B |
| Cache2 | 38.18 ns | 0.343 ns | 0.304 ns | 0.0019 | 0.0001 | 32 B |
| Cache3 | 40.21 ns | 0.170 ns | 0.151 ns | 0.0024 | 0.0001 | 40 B |
lycian
lycian3mo ago
depends on scale tbh. It's not really bad to prematurely optimize here if you know you'll ever only need one or two formats, but beyond that I'd have to see more discrepancy in benchmarks at the scale you're expecting to care
lycian
lycian3mo ago
System.Globalization.NumberFormatInfo class - .NET
Learn more about the System.Globalization.NumberFormatInfo class.
dreadfullydistinct
right, yeah I am fairly sure it would only ever be 2 or 3, and it's data I'm generating in my web backend and sending to be rendered into a UI Given that, there's an argument to make it culture specific, but I think retrieving the user's current culture is not really worth the hassle at this stage Scale is pretty small, this endpoint will probably only be hit a couple times an hour if that 😛 It definitely is not really worth it for this specific use case but I do like to learn more about .NET when these things come up