C#数值格式化:保留两位小数并补零的完整指南

📅 2026/7/30 6:10:46 👁️ 阅读次数 📝 编程学习
C#数值格式化:保留两位小数并补零的完整指南

1. 项目概述:为什么小数格式化是C#开发中的高频刚需?

在C#开发中,处理数值的显示格式,尤其是将数据统一为保留两位小数、不足补零的格式,是一个看似简单却无处不在的需求。无论是财务系统里的金额、物联网采集的传感器读数、报表工具中的数据展示,还是Web API接口返回给前端的标准化数值,我们都需要一个整洁、统一、无歧义的输出。想象一下,一个电商订单的总金额,如果一会儿显示“125.5”,一会儿显示“125.50”,不仅给用户带来困惑,在数据导出、对账等环节也可能引发不必要的麻烦。ToString格式化,正是解决这一问题的瑞士军刀。

这个需求的核心,远不止调用一个方法那么简单。它背后涉及类型转换的陷阱、性能的考量、全球化本地化的适配,以及不同场景(控制台、Web、数据库)下的细微差别。很多新手开发者会直接拼接字符串,或者使用Math.Round后简单转换,结果常常遇到四舍五入不准确、尾数0丢失、甚至因文化区域差异导致小数点符号错误等问题。本文将从一个资深C#开发者的视角,彻底拆解“保留两位小数并补零”这个需求,不仅给你可以直接“抄作业”的代码,更会深入背后的原理、不同方案的优劣对比,以及我在实际项目中踩过的坑和总结的最佳实践。

2. 核心需求解析与方案选型

2.1 需求本质:格式化,而非舍入

首先必须厘清一个关键概念:我们讨论的“保留两位小数”,首要目标是格式化显示,其次才是数值舍入。这两者经常被混淆,但目的截然不同。

  • 格式化显示:关心的是字符串的形态。例如,数值12.5格式化为“12.50”,数值12格式化为“12.00”。原数值的精度并没有改变,只是其字符串表示被规范了。
  • 数值舍入:改变的是数值本身。例如,12.567舍入到两位小数后,在内存中可能变成了12.57(取决于舍入规则),这是一个新的数值。

我们的标题要求“保留小数后两位,不足的补充0”,这明确指向了格式化显示。12.5变成“12.50”,是补零;12.567变成“12.57”,是四舍五入后再补零。因此,一个完整的解决方案需要同时处理舍入和格式化。

2.2 方案全景图:ToString格式字符串详解

C#为数值格式化提供了强大而灵活的标准数字格式字符串和自定义数字格式字符串。对于“两位小数补零”,以下几个格式字符串是核心:

  1. “F2” / “f2” (Fixed-point) - 最常用、最直观

    • 作用:定点格式。将数字转换为“-ddd.ddd…”形式的字符串,其中每个d代表一个数字。F后面的数字指定小数位数。
    • 示例12.5.ToString(“F2”)=>“12.50”12.ToString(“F2”)=>“12.00”12.5678.ToString(“F2”)=>“12.57”
    • 特点:自动进行四舍五入(默认采用MidpointRounding.ToEven,即“四舍六入五成双”的银行家舍入法,这是.NET的默认行为,稍后详解),并自动补零。这是满足标题需求的首选方案
  2. “N2” / “n2” (Number) - 适合含千位分隔符的显示

    • 作用:数字格式。与F类似,但会加入当前文化区域的千位分隔符。
    • 示例:在中文区域,1234.5.ToString(“N2”)=>“1,234.50”
    • 特点:如果不需要千位分隔符,用F2更干净;如果需要,N2是标准做法。
  3. “0.00” (自定义格式) - 高度可控

    • 作用:自定义格式占位符。0是零占位符,如果数字存在则显示,不存在则显示0
    • 示例12.5.ToString(“0.00”)=>“12.50”12.ToString(“0.00”)=>“12.00”
    • 特点:与F2效果几乎一致,但在构建更复杂的格式(如混合固定文本、条件格式)时更灵活。
  4. “C2” / “c2” (Currency) - 货币专用

    • 作用:货币格式。会附加当前文化区域的货币符号,并通常包含千位分隔符和两位小数。
    • 示例:在中文区域,12.5.ToString(“C2”)=>“¥12.50”
    • 特点切勿滥用。仅当明确表示货币金额时使用,否则会带来误导。

实操心得:默认舍入规则——“银行家舍入法”这是最容易踩坑的地方。ToString(“F2”)Math.Round(value, 2)默认使用MidpointRounding.ToEven。对于5的处理规则是:看前一位数字,如果是奇数则入,是偶数则舍。例如:

  • 1.235->1.24(3是奇数,5入)
  • 1.245->1.24(4是偶数,5舍) 这与我们熟知的“四舍五入”不同。在财务等要求严格四舍五入的场景,必须指定舍入方式:
// 使用标准的“四舍五入”(当5时始终入) value.ToString(“F2”, CultureInfo.InvariantCulture); // 但ToString本身不提供舍入方式参数,需先处理数值 // 正确做法:先使用Math.Round指定舍入方式 Math.Round(value, 2, MidpointRounding.AwayFromZero).ToString(“F2”);

2.3 方案选型决策表

为了帮你快速决策,我将不同场景下的推荐方案总结如下:

场景描述推荐方案代码示例理由与注意事项
通用场景,简单显示两位小数“F2”格式字符串double value = 12.5; string result = value.ToString(“F2”);最简洁、意图最清晰,自动处理舍入与补零。
需要千位分隔符的数值显示“N2”格式字符串double value = 1234567.5; string result = value.ToString(“N2”); // “1,234,567.50”符合大数字的阅读习惯。
明确表示货币金额“C2”格式字符串decimal price = 99.95m; string result = price.ToString(“C2”); // “¥99.95”标准货币格式,包含符号。注意文化区域影响。
需要严格“四舍五入”Math.Round+“F2”double value = 1.235; string result = Math.Round(value, 2, MidpointRounding.AwayFromZero).ToString(“F2”); // “1.24”先使用Math.Round控制舍入规则,再格式化。金融计算常用
构建复杂格式(如单位拼接)自定义格式“0.00”double temp = 36.5; string result = temp.ToString(“0.00 °C”); // “36.50 °C”格式字符串中可直接嵌入文本,更灵活。
高性能循环或大量格式化String.FormatStringBuilderstring result = String.Format(“{0:F2}”, value);在循环中,String.Format有时比多次调用value.ToString()性能稍好,但差异通常不大,可读性优先。
Web API JSON 响应格式化配置 Json 序列化器services.AddControllers().AddJsonOptions(...)在后端全局配置,避免在每个属性上手动格式化。详见后续章节。

3. 核心细节解析与实操要点

3.1 数据类型的选择:decimalvsdouble

在你开始格式化之前,选择正确的数值类型是地基。对于涉及金额、高精度要求的计算,强烈推荐使用decimal,而不是doublefloat

  • double/float(双精度/单精度浮点数):基于IEEE 754标准,用于科学计算。它们表示的数在十进制下可能是不精确的。例如,0.1在二进制中是循环小数,在double中无法精确存储。这会导致(0.1 + 0.2).ToString(“F2”)可能得到“0.30”,但(0.1 + 0.2) == 0.3的比较返回false。这对于财务计算是灾难。
  • decimal(十进制浮点数):专门为财务和货币计算设计。它用整数和比例因子来表示十进制数,因此可以精确表示像0.1这样的十进制分数。对于任何与钱有关的计算,无条件使用decimal
// 错误示范:使用double进行金融计算 double doubleAmount = 0.1; doubleAmount += 0.2; // 实际内存值可能为0.30000000000000004 Console.WriteLine(doubleAmount.ToString(“F2”)); // 输出 “0.30”,但这是“虚假”的精确。 Console.WriteLine(doubleAmount == 0.3); // 输出 False // 正确示范:使用decimal decimal decimalAmount = 0.1m; // 注意 ‘m’ 后缀 decimalAmount += 0.2m; Console.WriteLine(decimalAmount.ToString(“F2”)); // 输出 “0.30” Console.WriteLine(decimalAmount == 0.3m); // 输出 True

3.2 文化区域(Culture)的陷阱与掌控

格式化行为严重依赖于当前的CultureInfo(文化区域信息)。最大的坑就是小数点符号

  • 美国英语(en-US)文化中,小数点符号是点(.),千位分隔符是逗号(,)。
  • 法国法语(fr-FR)德语(de-DE)等许多欧洲文化中,小数点符号是逗号(,),千位分隔符是点(.)或空格。

如果你的代码在服务器上运行,而服务器的区域设置与你开发机不同,ToString(“F2”)可能会产生用逗号作为小数点的字符串(如“12,50”)。当这个字符串被发送到前端JavaScript进行解析,或存入数据库的DECIMAL字段时,就会引发解析错误。

解决方案:使用不变文化(InvariantCulture)对于机器间交换的数据(如Web API、配置文件、数据库交互),应使用CultureInfo.InvariantCulture。它基于美国英语文化,但去除了所有本地化特性,使用点(.)作为小数点,且没有千位分隔符,保证了格式的稳定性和可预测性。

double value = 1234.56; // 依赖当前系统文化(危险) string localString = value.ToString(“N2”); // 在中文系统可能是 “1,234.56”,在法文系统可能是 “1 234,56” // 使用不变文化(安全,用于数据交换) string invariantString = value.ToString(“N2”, CultureInfo.InvariantCulture); // 永远是 “1,234.56” // 仅格式化小数位,不使用千位分隔符(更通用的数据交换格式) string invariantFString = value.ToString(“F2”, CultureInfo.InvariantCulture); // 永远是 “1234.56”

注意事项:UI显示需区分场景

  1. 后台数据处理、API接口、日志记录:一律使用CultureInfo.InvariantCulture
  2. 前端用户界面显示:应使用当前用户的本地文化(CultureInfo.CurrentCulture),让数字以用户习惯的方式呈现(如Thread.CurrentThread.CurrentCulture = new CultureInfo(“fr-FR”))。
  3. 数据库存储:最佳实践是存储原始的decimal数值,而不是格式化后的字符串。格式化应在展示层进行。

3.3 性能考量:大量数据格式化

在需要格式化海量数据(例如生成CSV报告、处理实时数据流)时,性能变得重要。虽然单次ToString调用开销很小,但在百万次循环中,细微差别也会被放大。

  • 基准测试:在循环中,$“{value:F2}”(字符串插值)、String.Format(“{0:F2}”, value)value.ToString(“F2”)的性能在同一数量级,ToString通常略快。差异通常不构成瓶颈。
  • 真正的影响因子
    • 装箱(Boxing):如果valueobject类型或值类型被当作object传递(如在String.Format中使用params object[]),会发生装箱拆箱,影响性能。确保直接对数值类型调用方法。
    • 文化信息传递:频繁创建CultureInfo对象会有开销。如果需要固定文化,应将其缓存起来。
    • 字符串拼接:避免在循环内使用+=拼接大量格式化后的字符串,这会产生大量中间字符串对象,导致GC压力。应使用StringBuilder
// 低效做法 List<double> hugeList = GetHugeDataList(); string report = “”; foreach (var num in hugeList) { report += num.ToString(“F2”) + “,”; // 每次循环都产生新字符串对象 } // 高效做法 StringBuilder sb = new StringBuilder(); CultureInfo ci = CultureInfo.InvariantCulture; // 缓存文化信息 foreach (var num in hugeList) { sb.Append(num.ToString(“F2”, ci)).Append(‘,’); } string report = sb.ToString();

4. 实操过程与核心环节实现

4.1 基础格式化代码实战

让我们从最简单的控制台应用开始,看看各种格式化方法如何工作。

using System; using System.Globalization; class Program { static void Main() { decimal price = 19.99m; double weight = 60.5; int quantity = 100; Console.WriteLine(“=== 基础格式化演示 ===”); Console.WriteLine($“价格 (decimal F2): {price:F2}”); // 字符串插值 Console.WriteLine($“价格 (decimal ToString): {price.ToString(“F2”)}”); Console.WriteLine($“重量 (double F2): {weight:F2}”); Console.WriteLine($“重量 (double 0.00): {weight.ToString(“0.00”)}”); Console.WriteLine($“数量 (int F2): {quantity:F2}”); // 整数也会补零 “100.00” Console.WriteLine(“\n=== 文化区域影响演示 ===”); var usCulture = new CultureInfo(“en-US”); var frCulture = new CultureInfo(“fr-FR”); Console.WriteLine($“US: {price.ToString(“N2”, usCulture)}”); // “19.99” Console.WriteLine($“FR: {price.ToString(“N2”, frCulture)}”); // “19,99” Console.WriteLine($“Invariant: {price.ToString(“F2”, CultureInfo.InvariantCulture)}”); // “19.99” Console.WriteLine(“\n=== 舍入规则演示 ===”); double[] testValues = { 1.234, 1.235, 1.245, 1.255 }; Console.WriteLine(“值 | 默认(F2) | 四舍五入(Math.Round + F2)”); foreach (var val in testValues) { var defaultFormat = val.ToString(“F2”); var roundAway = Math.Round(val, 2, MidpointRounding.AwayFromZero).ToString(“F2”); Console.WriteLine($“{val} | {defaultFormat} | {roundAway}”); } } }

4.2 在Web API(ASP.NET Core)中全局格式化响应

在Web开发中,我们通常定义模型(Model),然后由框架自动序列化为JSON。如何让所有decimal属性在JSON响应中自动格式化为两位小数?

错误做法:在模型属性里调用ToString,这会改变属性的类型(从decimal变成string),破坏数据契约,并影响后续计算。

正确做法:在ASP.NET Core的启动配置中,全局配置JSON序列化选项。

  1. 安装必要的NuGet包(如果尚未安装):

    Install-Package Newtonsoft.Json

    或使用System.Text.Json(.NET Core 3.0+内置,推荐)。

  2. 使用System.Text.Json配置(推荐,性能更好): 在Program.csStartup.ConfigureServices中:

    builder.Services.AddControllers() .AddJsonOptions(options => { // 配置所有decimal属性序列化为带两位小数的字符串 options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals; // 非必需,但允许配置 // 更精细的控制:使用自定义转换器 options.JsonSerializerOptions.Converters.Add(new DecimalToStringFormatter()); }); // 自定义转换器 public class DecimalToStringFormatter : JsonConverter<decimal> { public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { // 反序列化:从JSON字符串读取decimal return reader.GetDecimal(); } public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options) { // 序列化:将decimal格式化为两位小数的字符串 writer.WriteStringValue(value.ToString(“F2”, CultureInfo.InvariantCulture)); } }
  3. 使用Newtonsoft.Json配置(传统项目)

    services.AddControllers() .AddNewtonsoftJson(options => { options.SerializerSettings.ContractResolver = new DefaultContractResolver(); options.SerializerSettings.Formatting = Formatting.Indented; // 为decimal类型指定自定义转换器 options.SerializerSettings.Converters.Add(new DecimalFormatConverter()); }); public class DecimalFormatConverter : JsonConverter<decimal> { public override decimal ReadJson(JsonReader reader, Type objectType, decimal existingValue, bool hasExistingValue, JsonSerializer serializer) { return Convert.ToDecimal(reader.Value); } public override void WriteJson(JsonWriter writer, decimal value, JsonSerializer serializer) { // 直接写入格式化后的字符串 writer.WriteValue(value.ToString(“F2”, CultureInfo.InvariantCulture)); } public override bool CanRead => true; }

配置完成后,你的API返回的模型中的所有decimal属性在JSON中都会自动显示为两位小数格式。

4.3 在WPF、WinForms数据绑定中格式化显示

在桌面应用中,数据绑定是显示数据的核心。直接在ViewModel或Model的属性里返回格式化字符串会破坏数据绑定更新机制。正确的做法是使用值转换器(Value Converter)或绑定字符串格式。

WPF 使用 StringFormat:

<TextBlock Text=“{Binding Price, StringFormat=F2}”/> <!-- 或添加固定文本 --> <TextBlock Text=“{Binding Price, StringFormat={}{0:F2} 元}”/> <!-- 使用不变文化 --> <TextBlock Text=“{Binding Price, StringFormat=F2, ConverterCulture=‘en-US’}”/>

WPF 使用自定义值转换器(更灵活):

// 实现 IValueConverter [ValueConversion(typeof(decimal), typeof(string))] public class DecimalToTwoDecimalStringConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is decimal decimalValue) { // 使用绑定传递的文化,或默认使用不变文化 return decimalValue.ToString(“F2”, culture ?? CultureInfo.InvariantCulture); } return value?.ToString() ?? string.Empty; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { // 将UI字符串转回decimal,需要处理解析失败 if (string.IsNullOrWhiteSpace(value as string)) return 0m; if (decimal.TryParse(value as string, NumberStyles.Any, culture, out decimal result)) return result; return 0m; // 或抛出异常,或返回 DependencyProperty.UnsetValue } }

在XAML中引用:

<Window.Resources> <local:DecimalToTwoDecimalStringConverter x:Key=“DecimalConverter”/> </Window.Resources> <TextBlock Text=“{Binding Price, Converter={StaticResource DecimalConverter}}”/>

WinForms 使用 DataGridView 的单元格格式化:

dataGridView1.Columns[“PriceColumn”].DefaultCellStyle.Format = “F2”; // 或者针对特定行/列设置 dataGridView1.Rows[0].Cells[“Price”].Style.Format = “N2”;

4.4 数据库查询中的格式化(SQL vs C#)

数据常存储在数据库中。格式化应该在哪个层面进行?

  • 原则存储原始精度,展示时格式化。在数据库(如SQL Server)中,应使用DECIMAL(p, s)NUMERIC(p, s)类型来精确存储数值,其中s指定了固定的小数位数。不要在数据库中存储格式化后的字符串。
  • 在SQL查询中格式化(不推荐在应用层逻辑中使用)
    -- SQL Server SELECT FORMAT(Price, ‘N2’) AS FormattedPrice FROM Products; -- 返回字符串,影响后续计算 -- 或使用 STR 函数 SELECT STR(Price, 10, 2) AS FormattedPrice FROM Products; -- 总长10位,小数2位
    缺点:在数据库层进行格式化,会将数值类型转换为字符串,破坏了类型,且格式受数据库服务器区域设置影响。这只应在生成即用型报表时考虑。
  • 推荐做法:在C#应用中从数据库读取decimal原始值,然后在业务逻辑层或展示层进行统一的格式化。

5. 常见问题与排查技巧实录

5.1 问题:格式化后小数位丢失或显示不正确

  • 症状:调用ToString(“F2”)后,结果不是预期的两位小数,或者小数部分全为0。
  • 排查步骤
    1. 检查原始值:首先在调试器中查看被格式化的变量值。确认它是一个浮点数(float,double,decimal)而不是整数(int)。123.ToString(“F2”)会得到“123.00”,这是正确的。如果你期望123.456但看到123.00,说明变量本身可能就被错误地赋值或计算为整数了。
    2. 检查舍入:记住默认的银行家舍入法。1.235.ToString(“F2”)得到“1.24”1.245.ToString(“F2”)得到“1.24”。如果这不是你想要的,你需要先用Math.Round指定舍入方式。
    3. 检查文化区域:如果你的代码在服务器或不同区域设置的机器上运行,ToString(“F2”)可能使用了逗号作为小数点。使用ToString(“F2”, CultureInfo.InvariantCulture)来锁定格式。

5.2 问题:性能瓶颈出现在大量数据循环中

  • 症状:生成包含数十万行格式化数据的报告或字符串时,程序变慢,内存占用高。
  • 优化技巧
    1. 使用StringBuilder:这是处理大量字符串拼接的铁律。永远不要在循环中使用+=拼接字符串。
    2. 缓存格式提供器:如果使用固定的文化信息(如InvariantCulture),在循环外创建并引用它,而不是每次都CultureInfo.InvariantCulture(虽然它是静态属性,但直接使用无妨,但自定义文化对象一定要缓存)。
    3. 考虑流式处理:对于超大数据集,考虑使用TextWriter(如StreamWriter)直接写入文件流,而不是在内存中构建完整的字符串。
    4. 并行化处理:如果数据行之间独立,可以考虑使用Parallel.ForEach进行并行格式化,但要注意线程安全和StringBuilder的非线程安全性。通常的做法是让每个线程处理一部分数据,生成局部字符串,最后合并。

5.3 问题:Web API返回的JSON中decimal没有格式化

  • 症状:已经在模型属性上用了[DisplayFormat(DataFormatString = “{0:F2}”)]之类的标签,但API返回的JSON里还是19.99而不是“19.99”
  • 原因[DisplayFormat]特性主要用于ASP.NET MVC的视图渲染和模型验证错误信息显示,不控制JSON序列化。[DataType(DataType.Currency)]等特性同理。
  • 解决方案
    • 方案A(全局):如前所述,在AddJsonOptions中配置全局的JsonConverter(推荐)。
    • 方案B(局部):如果只想对特定属性格式化,可以创建一个具有string类型 getter 的属性,该 getter 返回格式化后的字符串,并在序列化时忽略原始decimal属性。
      public class ProductDto { [JsonIgnore] // 使用Newtonsoft.Json时用 [JsonIgnore] public decimal Price { get; set; } public string PriceFormatted => Price.ToString(“F2”, CultureInfo.InvariantCulture); }
      注意:此方法会创建一个只读的格式化属性,反序列化时需要额外处理。

5.4 问题:从格式化字符串反向解析回数值失败

  • 场景:用户在前端输入了“12,50”(逗号小数点),或在文本文件中读取了格式化的数字字符串,需要转回decimal进行计算。
  • 解决方案:使用decimal.Parsedecimal.TryParse的重载方法,明确指定文化信息。
    string inputFromFrenchUI = “12,50”; string inputFromDataFile = “12.50”; CultureInfo frenchCulture = new CultureInfo(“fr-FR”); CultureInfo invariantCulture = CultureInfo.InvariantCulture; // 解析法国格式 decimal value1 = decimal.Parse(inputFromFrenchUI, frenchCulture); // 解析数据文件格式(不变文化) decimal value2 = decimal.Parse(inputFromDataFile, invariantCulture); // 安全解析 if (decimal.TryParse(inputFromFrenchUI, NumberStyles.Any, frenchCulture, out decimal safeValue)) { // 解析成功 }
    关键点NumberStyles.Any允许数字字符串包含千位分隔符、小数点、货币符号等,再结合正确的CultureInfo,可以解析大多数格式。

5.5 独家避坑技巧:一个健壮的格式化辅助类

在实际项目中,我通常会封装一个静态工具类来统一处理格式化,确保一致性并减少错误。

using System.Globalization; public static class NumberFormatter { // 使用不变文化作为默认数据交换格式 private static readonly IFormatProvider DefaultFormatProvider = CultureInfo.InvariantCulture; /// <summary> /// 将数值格式化为保留两位小数、不足补零的字符串(用于数据交换)。 /// 使用银行家舍入法。 /// </summary> public static string ToFixedTwoDecimalString(this decimal value) { return value.ToString(“F2”, DefaultFormatProvider); } /// <summary> /// 将数值格式化为保留两位小数、不足补零的字符串(用于数据交换)。 /// 使用银行家舍入法。 /// </summary> public static string ToFixedTwoDecimalString(this double value) { // 注意:double有精度问题,金融计算请勿使用。 return value.ToString(“F2”, DefaultFormatProvider); } /// <summary> /// 将数值格式化为保留两位小数、不足补零的字符串(用于数据交换),并指定舍入方式。 /// </summary> public static string ToFixedTwoDecimalString(this decimal value, MidpointRounding rounding) { var roundedValue = Math.Round(value, 2, rounding); return roundedValue.ToString(“F2”, DefaultFormatProvider); } /// <summary> /// 从字符串安全解析为decimal,支持多种数字格式。 /// </summary> public static bool TryParseDecimal(string input, out decimal result) { // 先尝试用不变文化(点号小数点) if (decimal.TryParse(input, NumberStyles.Any, DefaultFormatProvider, out result)) return true; // 再尝试用当前UI文化 if (decimal.TryParse(input, NumberStyles.Any, CultureInfo.CurrentCulture, out result)) return true; // 最后尝试移除常见千位分隔符后再解析 string cleaned = input?.Replace(“,”, “”).Replace(“ “, “”); return decimal.TryParse(cleaned, NumberStyles.Any, DefaultFormatProvider, out result); } /// <summary> /// 为UI显示格式化,使用当前用户文化。 /// </summary> public static string ToLocalizedTwoDecimalString(this decimal value) { return value.ToString(“N2”, CultureInfo.CurrentCulture); } }

使用这个类,你可以像这样调用:var formatted = myPrice.ToFixedTwoDecimalString();。它把文化、格式、舍入规则等细节都隐藏起来,让业务代码更清晰,也更容易在将来统一修改格式策略。