You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用OpenXML读取带文本格式Excel单元格时浮点精度异常问题

问题根因

OpenXML直接读取CellValue.InnerText拿到的是xlsx文件中存储的原始值。Excel采用IEEE 754双精度浮点数存储数值,十进制的1.1无法被双精度精确表示,底层存储的就是1.1000000000000001这个近似值。
Excel界面显示正常,是因为它会根据单元格绑定的数字格式规则,对原始值做格式化渲染后再展示,不会直接输出原始存储值。你看到的单元格属性s="2"就是样式索引,指向样式表中对应的数字格式配置,你当前的代码完全跳过了样式解析和格式化步骤,自然会拿到带精度误差的原始值。

实现方案

要拿到和Excel界面一致的显示值,核心是按照Excel的渲染逻辑走:读取原始数值后,根据单元格关联的数字格式对数值做格式化,而不是直接输出原始字符串。
具体实现步骤:

  • 加载工作簿样式表,同时内置Excel预定义的数字格式映射(索引小于164的格式为Excel内置格式)
  • 遍历单元格时,对数值类型的单元格先将原始值解析为double类型
  • 根据单元格的StyleIndex找到对应的数字格式码
  • 按照格式码格式化double值,常规格式下使用G10格式说明符即可自动消除浮点数精度带来的多余小数位,输出和Excel一致的1.1这类结果

修正后的完整读取代码如下:

// Excel内置数字格式映射(索引<164为内置格式,可根据业务场景补充)
private static readonly Dictionary<uint, string> BuiltInNumberFormats = new Dictionary<uint, string>
{
    {0, "General"},
    {1, "0"},
    {2, "0.00"},
    {3, "#,##0"},
    {4, "#,##0.00"},
    {9, "0%"},
    {10, "0.00%"},
    {11, "0.00E+00"},
    {14, "M/d/yyyy"},
    {18, "h:mm tt"},
    {20, "H:mm"},
    {22, "M/d/yyyy H:mm"}
};

// 读取逻辑
List<List<string>> rows = new List<List<string>>();
// using块自动释放资源,无需手动调用Close
using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(excelFilePath, false))
{
    WorkbookPart workbookPart = spreadsheetDocument.WorkbookPart;
    WorksheetPart worksheetPart = workbookPart.WorksheetParts.First();
    SheetData sheetData = worksheetPart.Worksheet.Elements<SheetData>().First();

    SharedStringTablePart sstpart = workbookPart.GetPartsOfType<SharedStringTablePart>().FirstOrDefault();
    SharedStringTable sst = sstpart?.SharedStringTable;

    // 加载样式表和所有数字格式
    WorkbookStylesPart stylesPart = workbookPart.WorkbookStylesPart;
    Stylesheet styles = stylesPart.Stylesheet;
    Dictionary<uint, string> numberFormats = new Dictionary<uint, string>(BuiltInNumberFormats);
    // 加载用户自定义数字格式
    if (styles.NumberingFormats != null)
    {
        foreach (NumberingFormat nf in styles.NumberingFormats.Elements<NumberingFormat>())
        {
            numberFormats.TryAdd(nf.NumberFormatId.Value, nf.FormatCode);
        }
    }

    foreach (Row r in sheetData.Elements<Row>())
    {
        List<string> cols = new List<string>();
        foreach (Cell c in r.Elements<Cell>())
        {
            // 处理共享字符串类型
            if (c.DataType != null && c.DataType == CellValues.SharedString)
            {
                if (sst != null && int.TryParse(c.CellValue?.Text, out int ssid) && ssid < sst.ChildElements.Count)
                {
                    cols.Add(sst.ChildElements[ssid].InnerText);
                }
                else
                {
                    cols.Add(c.CellValue?.InnerText);
                }
            }
            // 处理数值类型单元格
            else if (c.DataType == null || c.DataType == CellValues.Number)
            {
                string rawValue = c.CellValue?.InnerText;
                if (double.TryParse(rawValue, NumberStyles.Any, CultureInfo.InvariantCulture, out double numVal))
                {
                    string formatCode = "General";
                    // 查找单元格绑定的数字格式
                    if (c.StyleIndex != null && c.StyleIndex.Value < styles.CellFormats.Count)
                    {
                        CellFormat cellFormat = (CellFormat)styles.CellFormats.ElementAt((int)c.StyleIndex.Value);
                        if (cellFormat.NumberFormatId != null && numberFormats.TryGetValue(cellFormat.NumberFormatId.Value, out string foundFormat))
                        {
                            formatCode = foundFormat;
                        }
                    }
                    string displayVal;
                    if (formatCode == "General")
                    {
                        // G10格式匹配Excel常规数值渲染规则,自动过滤双精度精度误差
                        displayVal = numVal.ToString("G10", CultureInfo.CurrentCulture);
                    }
                    else
                    {
                        displayVal = numVal.ToString(formatCode, CultureInfo.CurrentCulture);
                    }
                    cols.Add(displayVal);
                }
                else
                {
                    cols.Add(rawValue);
                }
            }
            else
            {
                cols.Add(c.CellValue?.InnerText);
            }
        }
        rows.Add(cols);
    }
}

注意事项

  • G10格式说明符最多保留10位有效数字,正好匹配Excel常规格式的显示逻辑,不需要手动写截断小数位的代码,不会误截断有效数字,也能自动把1.1000000000000001这类误差值修正为1.1。
  • 代码补充了空值、索引越界判断,修复了原代码中SharedStringTable不存在时抛出空引用异常的问题。
  • 如果需要处理特殊自定义格式(比如带前后缀的数值、特殊日期格式),只需要补充Excel格式码到.NET数字格式码的转换逻辑即可,常规数值、百分比、日期场景上述代码可以直接使用。

内容的提问来源于stack exchange,提问作者Arthur

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 16:01:17