1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-21 21:00:42 +08:00
Files
osu-lazer/osu.Game/Extensions/NumberFormattingExtensions.cs
T
Bartłomiej Dach a29a5ab7e6 Allow NumberFormattingExtensions.ToStandardFormattedString() to accept culture
I had previously made it invariant in
https://github.com/ppy/osu/pull/32837, and in another instance of past
me being an asshole, I can't actually find the reasoning for this at
this time.

That said, you'd be excused for thinking "why does this matter"? Well,
this will fix https://github.com/ppy/osu/issues/35381, because that
failure only occurs when the user's culture is set to one that doesn't
use a decimal point (.) but rather a decimal comma (,). This messes with
framework, which uses the *current* culture to check for decimal
separator rather than invariant:

https://github.com/ppy/osu-framework/blob/d3226a7842487de43a0d989e1bb59a9ebbc479af/osu.Framework/Graphics/UserInterface/TextBox.cs#L106-L111

An alternative would be to change framework instead to always accept the
invariant decimal separator.

God I hate this culture crap.
2025-10-22 10:31:12 +02:00

55 lines
2.7 KiB
C#

// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Globalization;
using System.Numerics;
using osu.Game.Utils;
namespace osu.Game.Extensions
{
public static class NumberFormattingExtensions
{
/// <summary>
/// For a given numeric type, return a formatted string in the standard format we use for display everywhere.
/// </summary>
/// <param name="value">The numeric value.</param>
/// <param name="maxDecimalDigits">The maximum number of decimals to be considered in the original value.</param>
/// <param name="asPercentage">Whether the output should be a percentage. For integer types, 0-100 is mapped to 0-100%; for other types 0-1 is mapped to 0-100%.</param>
/// <param name="cultureInfo">The culture to use when formatting the value. Defaults to <see cref="CultureInfo.CurrentCulture"/> if not specified.</param>
/// <returns>The formatted output.</returns>
public static string ToStandardFormattedString<T>(this T value, int maxDecimalDigits, bool asPercentage = false, CultureInfo? cultureInfo = null) where T : struct, INumber<T>, IMinMaxValue<T>
{
cultureInfo ??= CultureInfo.CurrentCulture;
double floatValue = double.CreateTruncating(value);
decimal decimalPrecision = normalise(decimal.CreateTruncating(value), maxDecimalDigits);
// Find the number of significant digits (we could have less than maxDecimalDigits after normalize())
int significantDigits = FormatUtils.FindPrecision(decimalPrecision);
if (asPercentage)
{
if (value is int)
floatValue /= 100;
return floatValue.ToString($@"0.{new string('0', Math.Max(0, significantDigits - 2))}%", cultureInfo);
}
string negativeSign = Math.Round(floatValue, significantDigits) < 0 ? "-" : string.Empty;
return $"{negativeSign}{Math.Abs(floatValue).ToString($"N{significantDigits}", cultureInfo)}";
}
/// <summary>
/// Removes all non-significant digits, keeping at most a requested number of decimal digits.
/// </summary>
/// <param name="d">The decimal to normalize.</param>
/// <param name="sd">The maximum number of decimal digits to keep. The final result may have fewer decimal digits than this value.</param>
/// <returns>The normalised decimal.</returns>
private static decimal normalise(decimal d, int sd)
=> decimal.Parse(Math.Round(d, sd).ToString(string.Concat("0.", new string('#', sd)), CultureInfo.InvariantCulture), CultureInfo.InvariantCulture);
}
}