# exact_change

Compares two fixed-point observations of one quantity exactly (two blocks, two windows, two venues, two rates): each value is raw with its own decimals and both are rescaled to the larger decimals. Returns new - old in units, that change as a percent and in basis points of old, and new as a percent of old, as exact decimal text. No floating point; NULL input returns NULL.

Canonical HTML: <https://determica.com/docs/functions/exact_change>

- Kind: scalar function
- Category: [Fixed-point and AMM math](/docs/category/fixed-point-and-amm-math.md)
- Tags: Fixed Point Math, Deterministic, Offline, Types

Compares two fixed-point observations of one quantity exactly (two blocks, two windows, two venues, two rates): each value is raw with its own decimals and both are rescaled to the larger decimals. Returns new - old in units, that change as a percent and in basis points of old, and new as a percent of old, as exact decimal text. No floating point; NULL input returns NULL.

## Overload 1: exact_change(UINT256, INTEGER, UINT256, INTEGER)

```sql
exact_change(UINT256, INTEGER, UINT256, INTEGER)
```

UINT256 or INT256 values in any pairing; the change is signed either way.

- Kind: scalar
- Execution context: Offline
- Behavior: Transform
- Execution modes: Offline
- Input shape: Scalar Arguments
- Planning contract: Runtime
- Cardinality: One Per Input
- Determinism: Deterministic
- Return shape: Scalar
- Schema stability: Fixed
- Stability: Stable
- Side effects: None documented

### Inputs

- `old` (UINT256; required; positional) — UINT256 or INT256 raw value; the base of the percents and bps.
- `old_decimals` (INTEGER; required; positional) — INTEGER 0 to 77: old is old / 10^old_decimals units.
- `new` (UINT256; required; positional) — UINT256 or INT256 raw value; native integers need a cast to either.
- `new_decimals` (INTEGER; required; positional) — INTEGER 0 to 77; may differ from old_decimals.

### Returns

Exact change, percent, bps and share

STRUCT(change VARCHAR, change_percent VARCHAR, change_bps VARCHAR, share_percent VARCHAR): change = new - old at the larger decimals; change_percent = (new - old) / old * 100, change_bps = (new - old) / old * 10000, share_percent = new / old * 100, each to 6 decimal places truncated toward zero. Trailing zeros trimmed, '-' for negatives. The three ratios are NULL when old is 0. Decimals outside 0 to 77 error.

- `comparison` (STRUCT(change VARCHAR, change_percent VARCHAR, change_bps VARCHAR, share_percent VARCHAR))

Exact change, percent, bps and share. STRUCT(change VARCHAR, change_percent VARCHAR, change_bps VARCHAR, share_percent VARCHAR): change = new - old at the larger decimals; change_percent = (new - old) / old * 100, change_bps = (new - old) / old * 10000, share_percent = new / old * 100, each to 6 decimal places truncated toward zero. Trailing zeros trimmed, '-' for negatives. The three ratios are NULL when old is 0. Decimals outside 0 to 77 error.

### Examples

_Local SQL · Local_

```sql
SELECT (exact_change(1500000::UINT256, 6, 1200000::UINT256, 6)).change AS change;
-- => [{"change":"-0.3"}]
```

## Overload 2: exact_change(INT256, INTEGER, INT256, INTEGER)

```sql
exact_change(INT256, INTEGER, INT256, INTEGER)
```

UINT256 or INT256 values in any pairing; the change is signed either way.

- Kind: scalar
- Execution context: Offline
- Behavior: Transform
- Execution modes: Offline
- Input shape: Scalar Arguments
- Planning contract: Runtime
- Cardinality: One Per Input
- Determinism: Deterministic
- Return shape: Scalar
- Schema stability: Fixed
- Stability: Stable
- Side effects: None documented

### Inputs

- `old` (INT256; required; positional) — UINT256 or INT256 raw value; the base of the percents and bps.
- `old_decimals` (INTEGER; required; positional) — INTEGER 0 to 77: old is old / 10^old_decimals units.
- `new` (INT256; required; positional) — UINT256 or INT256 raw value; native integers need a cast to either.
- `new_decimals` (INTEGER; required; positional) — INTEGER 0 to 77; may differ from old_decimals.

### Returns

Exact change, percent, bps and share

STRUCT(change VARCHAR, change_percent VARCHAR, change_bps VARCHAR, share_percent VARCHAR): change = new - old at the larger decimals; change_percent = (new - old) / old * 100, change_bps = (new - old) / old * 10000, share_percent = new / old * 100, each to 6 decimal places truncated toward zero. Trailing zeros trimmed, '-' for negatives. The three ratios are NULL when old is 0. Decimals outside 0 to 77 error.

- `comparison` (STRUCT(change VARCHAR, change_percent VARCHAR, change_bps VARCHAR, share_percent VARCHAR))

Exact change, percent, bps and share. STRUCT(change VARCHAR, change_percent VARCHAR, change_bps VARCHAR, share_percent VARCHAR): change = new - old at the larger decimals; change_percent = (new - old) / old * 100, change_bps = (new - old) / old * 10000, share_percent = new / old * 100, each to 6 decimal places truncated toward zero. Trailing zeros trimmed, '-' for negatives. The three ratios are NULL when old is 0. Decimals outside 0 to 77 error.

### Examples

_Local SQL · Local_

```sql
SELECT (exact_change(2410::INT256, 0, 2400::INT256, 0)).change_bps AS bps;
-- => [{"bps":"-41.493775"}]
```

## Overload 3: exact_change(INT256, INTEGER, UINT256, INTEGER)

```sql
exact_change(INT256, INTEGER, UINT256, INTEGER)
```

UINT256 or INT256 values in any pairing; the change is signed either way.

- Kind: scalar
- Execution context: Offline
- Behavior: Transform
- Execution modes: Offline
- Input shape: Scalar Arguments
- Planning contract: Runtime
- Cardinality: One Per Input
- Determinism: Deterministic
- Return shape: Scalar
- Schema stability: Fixed
- Stability: Stable
- Side effects: None documented

### Inputs

- `old` (INT256; required; positional) — UINT256 or INT256 raw value; the base of the percents and bps.
- `old_decimals` (INTEGER; required; positional) — INTEGER 0 to 77: old is old / 10^old_decimals units.
- `new` (UINT256; required; positional) — UINT256 or INT256 raw value; native integers need a cast to either.
- `new_decimals` (INTEGER; required; positional) — INTEGER 0 to 77; may differ from old_decimals.

### Returns

Exact change, percent, bps and share

STRUCT(change VARCHAR, change_percent VARCHAR, change_bps VARCHAR, share_percent VARCHAR): change = new - old at the larger decimals; change_percent = (new - old) / old * 100, change_bps = (new - old) / old * 10000, share_percent = new / old * 100, each to 6 decimal places truncated toward zero. Trailing zeros trimmed, '-' for negatives. The three ratios are NULL when old is 0. Decimals outside 0 to 77 error.

- `comparison` (STRUCT(change VARCHAR, change_percent VARCHAR, change_bps VARCHAR, share_percent VARCHAR))

Exact change, percent, bps and share. STRUCT(change VARCHAR, change_percent VARCHAR, change_bps VARCHAR, share_percent VARCHAR): change = new - old at the larger decimals; change_percent = (new - old) / old * 100, change_bps = (new - old) / old * 10000, share_percent = new / old * 100, each to 6 decimal places truncated toward zero. Trailing zeros trimmed, '-' for negatives. The three ratios are NULL when old is 0. Decimals outside 0 to 77 error.

### Examples

_Local SQL · Local_

```sql
-- A Chainlink answer (8 decimals) against a pool quote (6 decimals)
SELECT c.* FROM (SELECT exact_change(200000000000::INT256, 8, 2050500000::UINT256, 6) AS c);
-- => [{"change":"50.5","change_percent":"2.525","change_bps":"252.5","share_percent":"102.525"}]
```

## Overload 4: exact_change(UINT256, INTEGER, INT256, INTEGER)

```sql
exact_change(UINT256, INTEGER, INT256, INTEGER)
```

UINT256 or INT256 values in any pairing; the change is signed either way.

- Kind: scalar
- Execution context: Offline
- Behavior: Transform
- Execution modes: Offline
- Input shape: Scalar Arguments
- Planning contract: Runtime
- Cardinality: One Per Input
- Determinism: Deterministic
- Return shape: Scalar
- Schema stability: Fixed
- Stability: Stable
- Side effects: None documented

### Inputs

- `old` (UINT256; required; positional) — UINT256 or INT256 raw value; the base of the percents and bps.
- `old_decimals` (INTEGER; required; positional) — INTEGER 0 to 77: old is old / 10^old_decimals units.
- `new` (INT256; required; positional) — UINT256 or INT256 raw value; native integers need a cast to either.
- `new_decimals` (INTEGER; required; positional) — INTEGER 0 to 77; may differ from old_decimals.

### Returns

Exact change, percent, bps and share

STRUCT(change VARCHAR, change_percent VARCHAR, change_bps VARCHAR, share_percent VARCHAR): change = new - old at the larger decimals; change_percent = (new - old) / old * 100, change_bps = (new - old) / old * 10000, share_percent = new / old * 100, each to 6 decimal places truncated toward zero. Trailing zeros trimmed, '-' for negatives. The three ratios are NULL when old is 0. Decimals outside 0 to 77 error.

- `comparison` (STRUCT(change VARCHAR, change_percent VARCHAR, change_bps VARCHAR, share_percent VARCHAR))

Exact change, percent, bps and share. STRUCT(change VARCHAR, change_percent VARCHAR, change_bps VARCHAR, share_percent VARCHAR): change = new - old at the larger decimals; change_percent = (new - old) / old * 100, change_bps = (new - old) / old * 10000, share_percent = new / old * 100, each to 6 decimal places truncated toward zero. Trailing zeros trimmed, '-' for negatives. The three ratios are NULL when old is 0. Decimals outside 0 to 77 error.

### Examples

_Local SQL · Local_

```sql
SELECT coalesce((exact_change(0::UINT256, 18, 5::INT256, 18)).change_percent, 'no base') AS change_percent;
-- => [{"change_percent":"no base"}]
```

## Related functions

- [muldiv](/docs/functions/muldiv.md) — See Also
- [format_units](/docs/functions/format_units.md) — See Also
