# 公式

- Human documentation: [https://docs.univer.ai/zh-TW/guides/sheets/features/core/formula](https://docs.univer.ai/zh-TW/guides/sheets/features/core/formula)

- Agent Markdown: [https://docs.univer.ai/zh-TW/guides/sheets/features/core/formula.md](https://docs.univer.ai/zh-TW/guides/sheets/features/core/formula.md)

- Requested language: `zh-TW`

- Content language: `zh-TW`

- Documentation version: `1.2.0`

- Source: [sheets/features/core/formula.zh-TW.mdx](https://github.com/dream-num/documentation/blob/dev/content/guides/sheets/features/core/formula.zh-TW.mdx)

---

公式是 Univer 提供的核心能力之一，它可以讓你在儲存格中使用公式來計算數值，公式支援的函數和 Excel 一致，包括數學、邏輯、文字、日期等等。

如果工作簿中的公式數量較多，公式計算可能占用主執行緒並導致頁面卡頓。建議將公式計算放到 Worker 進程中執行，以降低對頁面渲染和互動的影響。具體配置方式請參考 [Web Workers](https://docs.univer.ai/zh-TW/guides/sheets/getting-started/worker.md) 章節。

## 配置

以下是公式外掛相關配置項的連結：

| 外掛                         | 配置項                                                                                   |
| -------------------------- | ------------------------------------------------------------------------------------- |
| `@univerjs/engine-formula` | [IUniverEngineFormulaConfig](https://docs.univer.ai/zh-TW/reference/packages/plugins/univerjs/engine-formula.md)     |
| `@univerjs/sheets-formula` | [IUniverSheetsFormulaBaseConfig](https://docs.univer.ai/zh-TW/reference/packages/plugins/univerjs/sheets-formula.md) |

使用 `initialFormulaComputing` 控制活頁簿載入時的計算方式。若已儲存的公式結果過期，使用 `CalculationMode.FORCED` 重新計算。預設模式下，相同選項放在 `formula` 內。

依接入模式將以下選項合併到既有的初始化設定中，保留其他外掛、語言、主題與容器設定，請勿重複註冊同一外掛。

#### 外掛模式

```typescript
import { CalculationMode, UniverSheetsFormulaPlugin } from '@univerjs/sheets-formula'

univer.registerPlugin(UniverSheetsFormulaPlugin, {
  initialFormulaComputing: CalculationMode.FORCED,
})
```

#### 預設模式

```typescript
import { CalculationMode, UniverSheetsCorePreset } from '@univerjs/preset-sheets-core'
import { createUniver } from '@univerjs/presets'

const { univerAPI } = createUniver({
  presets: [
    UniverSheetsCorePreset({
      formula: {
        initialFormulaComputing: CalculationMode.FORCED,
      },
    }),
  ],
})
```

* `CalculationMode.FORCED`: 重新計算所有公式。
* `CalculationMode.WHEN_EMPTY`: 預設值：只計算沒有已儲存 `v` 值的公式儲存格。
* `CalculationMode.NO_CALCULATION`: 初始化時不計算公式。

## 跨活頁簿引用

跨活頁簿公式可在 `Report` 中彙總 `Sales.xlsx` 的資料。與同一活頁簿內的跨工作表引用不同，它還需指定來源活頁簿。

### 引用語法

| 情境          | 範例                                    | 意義                           |
| ----------- | ------------------------------------- | ---------------------------- |
| 同一活頁簿的其他工作表 | `=SUM(Data!A1:A10)`                   | 目前活頁簿的 Data 工作表              |
| 其他活頁簿的儲存格範圍 | `=SUM('[Sales.xlsx]Data'!A1:A10)`     | Sales.xlsx 的 Data 工作表        |
| 其他活頁簿的表格    | `=SUM(Sales.xlsx!SalesTable[Amount])` | 已存在的 SalesTable 表格的 Amount 欄 |

`Sales.xlsx` 是活頁簿名稱，不是檔案路徑或下載網址。寫入公式不會讀取磁碟、下載 Excel 或連接另一個瀏覽器頁面。結構化引用需要先建立對應的[表格](https://docs.univer.ai/zh-TW/guides/sheets/features/table.md)。

### 引用同一實例中已載入的活頁簿

基礎公式引擎要求兩份活頁簿位於同一 Univer 實例與計算環境。以下程式碼在 Sheets 初始化後執行；外掛模式還需匯入本頁列出的 Facade 擴充。

```typescript
const source = univerAPI.createWorkbook({
  id: 'sales-workbook',
  name: 'Sales.xlsx',
  sheetOrder: ['sales-data'],
  sheets: {
    'sales-data': {
      id: 'sales-data',
      name: 'Data',
      rowCount: 100,
      columnCount: 20,
      cellData: {
        0: { 0: { v: 100 } },
        1: { 0: { v: 200 } },
      },
    },
  },
}, { makeCurrent: false })

const report = univerAPI.createWorkbook({ id: 'report-workbook', name: 'Report' })
const resultCell = report.getActiveSheet().getRange('A1')
resultCell.setFormula("=SUM('[Sales.xlsx]Data'!A1:A2)")

const formula = univerAPI.getFormula()
formula.executeCalculation()
await formula.onCalculationResultApplied()
console.log(resultCell.getValue()) // 300
```

`makeCurrent: false` 載入來源時不切換目前活頁簿。保留 `source`、`report` 可避免切換目前活頁簿後寫入錯誤位置。

名稱比對不區分大小寫，完整名稱優先；找不到時才忽略 `.xlsx`、`.xlsm` 等 Excel 副檔名。名稱不存在或不唯一時返回 `#REF!`，因此來源名稱應明確且唯一。

### 綁定固定的來源 ID

需要將公式名稱綁定到穩定文件 ID 時，可使用 `@univerjs-pro/engine-formula`。這是 Pro 擴充：先設定[用戶端授權](https://docs.univer.ai/zh-TW/server/license.md)，初始化時以 `UniverProFormulaEnginePlugin` 取代 `UniverFormulaEnginePlugin`，並匯入 Pro Facade。不要同時註冊兩個引擎；僅匯入 Facade 不會註冊外掛。

以下範例在已設定 Pro 引擎的環境中沿用上述活頁簿：

```typescript
import '@univerjs-pro/engine-formula/facade'

const reference = univerAPI.getFormula().buildReference({
  hostUnitId: report.getId(),
  unit: {
    unitId: source.getId(),
    formulaQualifier: 'Sales Source',
  },
  target: {
    kind: univerAPI.Enum.FormulaReferenceType.SHEET_RANGE,
    sheetName: 'Data',
    range: { startRow: 0, endRow: 1, startColumn: 0, endColumn: 0 },
  },
})

resultCell.setFormula(`=SUM(${reference})`)
```

`hostUnitId` 是儲存公式的活頁簿 ID，`unit.unitId` 是來源的穩定 ID，`formulaQualifier` 是公式中的公開名稱。座標從 0 開始，包含結束座標。

`buildReference()` 傳回不含 `=` 的引用片段並儲存綁定，不會載入來源或開始計算。資料仍需來自計算環境中已載入的活頁簿或已設定的資料提供者。手寫或由應用程式產生的公式應先綁定：

```typescript
const formula = univerAPI.getFormula()
const bound = formula.upsertExternalReference({
  unitId: report.getId(),
  qualifier: 'Sales Source',
  sourceUnitId: source.getId(),
  sourceUnitType: univerAPI.Enum.UniverInstanceType.UNIVER_SHEET,
})
if (!bound) throw new Error('Could not bind Sales Source')

resultCell.setFormula("=SUM('[Sales Source]Data'!A1:A2)")
```

相同綁定重複寫入是冪等操作。`qualifier` 不含括號或引號，也不可使用 `1`、`2` 等正整數；它們是 Excel 外部連結槽位，並非業務文件 ID。匯入外部連結時需保留連結資源，只複製公式文字並不足夠。

### 更新、儲存與卸載

透過 Facade 更新來源後，明確觸發計算並等待結果寫回，再讀取目標。不要假定 `setFormula()` 或 `setValue()` 返回時非同步計算已完成。

```typescript
const sourceSheet = source.getSheetBySheetId('sales-data')
if (!sourceSheet) throw new Error('Source sheet not found')
sourceSheet.getRange('A1').setValue(150)
formula.executeCalculation()
await formula.onCalculationResultApplied()
console.log(resultCell.getValue()) // 350
```

使用 `report.save()` 儲存含引用綁定資源的完整快照。只儲存公式文字會遺失名稱與來源 ID 的映射；來源資料需要另外儲存與載入。

來源卸載後，一般的已載入活頁簿引用無法再讀取它。Pro 引用能否繼續計算取決於資料提供者與可用快取；綁定不包含來源資料，也不保證離線計算。先前顯示的值不代表最新結果。

不再需要 Pro 綁定時可移除：

```typescript
univerAPI.getFormula().removeExternalReference({
  unitId: report.getId(),
  qualifier: 'Sales Source',
})
```

移除綁定不會刪除或改寫公式。下次計算重新解析名稱：若仍有同名的已載入活頁簿，可能繼續解析，否則返回 `#REF!`。若要停止引用，也應修改或清除公式。完整參數見 [FFormula 引用綁定 API](https://docs.univer.ai/zh-TW/reference/facade/formula.md)。

## 三維引用

三維引用涵蓋同一活頁簿內連續的工作表，與跨活頁簿引用不同：

```text
=SUM(Jan:Mar!B2:C3)
=SUM('Jan':'Mar'!B2)
```

範圍包含 Jan、Mar 與兩者之間的所有工作表，按活頁簿順序而非名稱排序。調整工作表順序時，應檢查彙總範圍是否仍正確。

## 已經支援的公式函數



## Facade API

完整 Facade API 請參閱 [FFormula](https://docs.univer.ai/zh-TW/reference/facade/formula.md)。

### 導入

> [!INFO: 外掛模式提示]
> 僅外掛模式需要手動引入 Facade 套件。預設模式已內建對應的 Facade 套件，無需額外引入。

```typescript
import '@univerjs/engine-formula/facade'
import '@univerjs/sheets-formula/facade'
import '@univerjs/sheets-formula-ui/facade'
```

### 開啟範圍選擇器

`@univerjs/sheets-formula-ui/facade` 會加入 `univerAPI.showRangeSelectorDialog`。如果自訂 UI 需要讓使用者選擇一個或多個儲存格範圍，可以複用公式相關面板使用的範圍選擇器。

```typescript
const workbook = univerAPI.getActiveWorkbook()
const worksheet = workbook?.getActiveSheet()

if (workbook && worksheet) {
  await univerAPI.showRangeSelectorDialog({
    unitId: workbook.getId(),
    subUnitId: worksheet.getSheetId(),
    initialValue: [
      {
        unitId: workbook.getId(),
        sheetName: worksheet.getSheetName(),
        range: worksheet.getRange('A1:B2').getRange(),
      },
    ],
    maxRangeCount: 2,
    supportAcrossSheet: true,
    callback: (ranges, isCancel) => {
      console.log(ranges, isCancel)
    },
  })
}
```

### 執行公式計算

```typescript
const formulaEngine = univerAPI.getFormula()
formulaEngine.executeCalculation()
```

### 停止公式計算

```typescript
const formulaEngine = univerAPI.getFormula()
formulaEngine.stopCalculation()
```

### 公式開始計算

```typescript
// 監聽公式計算開始事件
const formulaEngine = univerAPI.getFormula()
formulaEngine.calculationStart((forceCalculate) => {
  console.log(forceCalculate)
})
```

### 公式計算中

```typescript
// 監聽公式計算中事件
const formulaEngine = univerAPI.getFormula()
formulaEngine.calculationProcessing((stageInfo) => {
  console.log(stageInfo)
})
```

### 公式計算結束

```typescript
// 監聽公式計算結束事件
const formulaEngine = univerAPI.getFormula()
formulaEngine.calculationEnd((functionsExecutedState) => {
  console.log(functionsExecutedState)
})
```

### 公式計算結果應用

```typescript
// 監聽公式計算結果應用事件
const formulaEngine = univerAPI.getFormula()
formulaEngine.calculationResultApplied((result) => {
  console.log(result)
})

// 等待公式計算結果應用完成
await formulaEngine.onCalculationResultApplied()
```

### 設定初始化資料時公式的計算模式

```typescript
const formulaEngine = univerAPI.getFormula()
formulaEngine.setInitialFormulaComputing(CalculationMode.FORCED)
```

如果這個 API 在 `Starting` 生命週期之後呼叫，會在下次建立 Univer Sheet 時生效。

如果希望它在本次初始化 Univer Sheet 時生效，可以考慮在 `Ready` 生命週期之前呼叫它，比如：

```typescript
univerAPI.addEvent(univerAPI.Event.LifeCycleChanged, ({ stage }) => {
  if (stage === LifecycleStages.Starting) {
    const formulaEngine = univerAPI.getFormula()
    formulaEngine.setInitialFormulaComputing(CalculationMode.FORCED)
  }
})
```

### 反覆運算

反覆運算依活頁簿設定，並儲存在快照的 `calculationSettings` 欄位中。預設關閉：循環參照公式會保留快取值，沒有快取時使用零。需要計算刻意設計的循環參照模型時，再啟用此功能。

| 選項                     | 預設值     | 行為                                     |
| ---------------------- | ------- | -------------------------------------- |
| `iterativeCalculation` | `false` | 啟用循環參照的反覆運算。                           |
| `maxIterations`        | `100`   | 1 到 32767 之間的整數。                       |
| `maxChange`            | `0.001` | 有限正數，表示收斂閾值。當循環群組中每個數值結果的變化量皆不超過此值時停止。 |

透過活頁簿 Facade 一次提交三個設定。此命令支援復原、重做與協同同步；活頁簿不存在或無法編輯時傳回 `false`。無效設定會正規化為預設值。

```ts
const workbook = univerAPI.getActiveWorkbook()
if (!workbook) throw new Error('No active workbook')

const applied = workbook.setCalculationSettings({
  iterativeCalculation: true,
  maxIterations: 100,
  maxChange: 0.001,
})
if (!applied) throw new Error('Could not update calculation settings')

console.log(workbook.getCalculationSettings())
```

建立活頁簿時，也可以透過 `createWorkbook()` 的 `calculationSettings` 設定。各活頁簿獨立儲存自己的設定。公式工具列中的「計算選項」提供相同設定。

等待計算結果套用後，可讀取循環參照診斷。`getCircularReferences()` 不會觸發計算；結果包含工作表 ID、從零開始的列欄座標、循環群組 ID、反覆運算次數，以及可用的收斂資訊。

```ts
const formula = univerAPI.getFormula()
formula.executeCalculation()
await formula.onCalculationResultApplied()
console.table(workbook.getCircularReferences())
```

### 取得公式字串的計算結果

非同步執行一批公式字串並返回計算結果：

```typescript
const formulaEngine = univerAPI.getFormula()
const formulas = {
  Book1: {
    Sheet1: {
      2: {
        3: [
          // 完整公式：
          '=SUM(A1:A10) + SQRT(D7)',

          // 分解後的子公式（每個都可以獨立計算）：
          'SUM(A1:A10)', // 子公式1
          'SQRT(D7)', // 子公式2
          'A1:A10', // 範圍參考
          'D7', // 單元格參考
        ],
      },
      4: {
        5: ['=A2 + B2 + SQRT(C5)', 'A2', 'B2', 'SQRT(C5)'],
      },
    },
  },
}
const result = await formulaEngine.executeFormulas(formulas)
console.log(result)
```

## 自訂公式

Univer 支援自訂公式，請參考[自訂公式](/blog/custom-formula) 章節了解如何實作。

## 常見問題

### `HYPERLINK` 公式儲存格無法操作超連結

需要引入 Univer Sheets 的超連結外掛，見[超連結](https://docs.univer.ai/zh-TW/guides/sheets/features/hyper-link.md)模組。

## 公式值的語意

`ISBLANK(A1)` 檢查引用儲存格是否有內容，不只是檢查顯示結果是否為空。清空後的儲存格（即使保留格式）為空；傳回 `""` 的公式不為空。直接值 `ISBLANK("")` 也傳回 `FALSE`。`UNIQUE` 比較文字時不區分大小寫，保留首次出現的寫法。`PROPER` 識別 Unicode 字母，包括帶重音的拉丁字母、希臘字母和西里爾字母。`AVERAGE` 會彙總 `FILTER` 等函式傳回的完整陣列。

```text
=ISBLANK("")                         → FALSE
=UNIQUE({"Univer";"univer";"Sheets"})  → {"Univer";"Sheets"}
=PROPER("élise müller")               → Élise Müller
=AVERAGE({3;7})                      → 5
```
