Skip to content

Commit dc88f06

Browse files
committed
docs: architectural amendments for RFC-0104 v1.17 and RFC-0105 v2.14
Address deferred issues and architectural findings from code reviews: RFC-0104 v1.17 amendments: - A1: Unified runtime dispatch (type-specific opcodes reserved for future) - A2: Cross-type comparison via type promotion, not lossy f64 - A3: Division iterations 128 (was 256), update Golden Rule #3 - A4: Generic Op::Sqrt opcode for DFP sqrt - A5: Single casting truth (perform_cast delegates to cast_to_type) - A6: DETERMINISTIC VIEW deferred to separate RFC-0110 - A7: Minimum verification requirements RFC-0105 v2.14 amendments: - B1: Unified runtime dispatch (DQA opcodes reserved, never emitted) - B2: Cross-type numeric comparison via type promotion table - B3: Single casting truth (three paths → one implementation) - B4: Validation on DQA extraction (Dqa::new instead of direct construction) - B5: Persistence payload validation (DFP 24 bytes, DQA 16 bytes) - B6: Display and string representation (format_dqa/parse_string_to_dqa) - B7: Verification requirements table
1 parent 00b14db commit dc88f06

2 files changed

Lines changed: 185 additions & 6 deletions

File tree

rfcs/accepted/numeric/0104-deterministic-floating-point.md

Lines changed: 97 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -368,8 +368,10 @@ DFP_DIV(a, b):
368368
// Quotient accumulator
369369
quotient = 0u128
370370
371-
// Fixed 256 iterations for determinism
372-
for i in 0..256:
371+
// Fixed 128 iterations for determinism (sufficient with pre-scaling guarantee)
372+
// Pre-scaling ensures a.mantissa < b.mantissa, so 128 bits of quotient precision
373+
// yields 15 guard bits above the 113 we keep — sufficient for correct RNE rounding.
374+
for i in 0..128:
373375
// Shift dividend left by 1 (with carry between hi/lo)
374376
(dividend_hi, dividend_lo, carry) = shift_left_with_carry(
375377
dividend_hi, dividend_lo
@@ -388,7 +390,7 @@ DFP_DIV(a, b):
388390
quotient = quotient | 1
389391
// Else: quotient bit remains 0, dividend unchanged
390392
391-
// quotient now has 256-bit precision
393+
// quotient now has 128-bit precision
392394
// CRITICAL: Align quotient for round_to_113
393395
// Find MSB position in 256-bit quotient (0-255)
394396
quotient_msb = 255 - quotient.leading_zeros()
@@ -1468,7 +1470,7 @@ Recommended CI matrix: x86_64-linux, arm64-linux, macOS, wasm
14681470

14691471
2. **No f64 for SQRT Seed:** The initial approximation for SQRT must use bit-by-bit integer sqrt. Using `f64::sqrt(x)` as a seed is FORBIDDEN — it introduces non-determinism.
14701472

1471-
3. **No Iteration Short-Circuiting:** Execute ALL iterations as specified (256 for division, 226 for SQRT). Compilers must NOT elide "useless" iterations via "fast-math" flags.
1473+
3. **No Iteration Short-Circuiting:** Execute ALL iterations as specified (128 for division, 226 for SQRT). Compilers must NOT elide "useless" iterations via "fast-math" flags.
14721474

14731475
### Mission 1b: Additional Transcendental Functions (Future Phase)
14741476

@@ -1811,7 +1813,97 @@ None. DFP is a new type that does not modify existing FLOAT/DOUBLE behavior.
18111813
18121814
---
18131815

1814-
**Version:** 1.16
1816+
## Appendix: Implementation Architecture Amendment (v1.17)
1817+
1818+
> **Date:** 2026-04-01
1819+
> **Status:** Accepted amendment based on code review findings
1820+
1821+
### A1. Unified Runtime Dispatch (Resolves: S11, S12)
1822+
1823+
The original RFC specified type-specific DFP opcodes (`OP_DFP_ADD/SUB/MUL/DIV`). After implementation review, the architecture uses **unified runtime dispatch** as the primary path:
1824+
1825+
- All generic `Op::Add/Sub/Mul/Div/Mod/Neg/Abs` route through a single `arithmetic_op()` method
1826+
- `arithmetic_op()` performs runtime type detection (one byte comparison per operand)
1827+
- Type-specific opcodes (`OP_DFP_ADD`, etc.) are **reserved for future JIT optimization** but not emitted by the current compiler
1828+
1829+
**Rationale:** Runtime detection cost (one `match` on a byte) is negligible vs disk I/O in a database VM. The DQA review proved that type-specific opcodes (7 defined for DQA) become dead code when the compiler doesn't emit them.
1830+
1831+
**Compiler requirement:** The expression compiler MUST ensure all generic arithmetic opcodes route through the type-aware `arithmetic_op()` dispatch. Any new arithmetic opcode added to `Op` must include Extension type handling.
1832+
1833+
### A2. Cross-Type Numeric Comparison (Resolves: S7, F1)
1834+
1835+
**Previous behavior:** Cross-type comparison (DFP vs Integer/Float) used `as_float64().unwrap()`, which:
1836+
1. Panicked for Extension types (server crash)
1837+
2. Lost DFP's 113-bit precision via lossy f64 conversion
1838+
1839+
**New behavior:** Cross-type comparison uses **type promotion** instead of lossy conversion:
1840+
1841+
| Left Type | Right Type | Comparison Strategy |
1842+
|-----------|------------|---------------------|
1843+
| DFP | Integer | Promote Integer → DFP, compare in DFP space |
1844+
| DFP | Float | Promote Float → DFP, compare in DFP space |
1845+
| DFP | DFP | Same-type `compare_dfp()` |
1846+
| DFP | Quant | `Error::IncomparableTypes` (explicit CAST required) |
1847+
1848+
**Implementation:** `as_float64()` still supports DFP for backward compatibility but the `compare()` method uses dedicated promotion paths that avoid precision loss.
1849+
1850+
### A3. Division Iterations: 128 (Resolves: D1)
1851+
1852+
The implementation uses **128 iterations** (not 256) for the long division loop. This is mathematically sufficient:
1853+
1854+
- Pre-scaling guarantees `a.mantissa < b.mantissa`
1855+
- 128 iterations yield 128 bits of quotient precision
1856+
- 15 guard bits above the 113 kept — sufficient for correct RNE rounding
1857+
- Golden Rule #3 updated: 128 for division (was 256)
1858+
1859+
### A4. DFP SQRT Opcode (Resolves: S5)
1860+
1861+
Add `Op::Sqrt` as a generic opcode (not DFP-specific):
1862+
1863+
```
1864+
Op::Sqrt => {
1865+
let v = self.stack.pop().unwrap_or_else(Value::null_unknown);
1866+
let result = match v {
1867+
Value::Integer(_) => Value::Null(DataType::Integer), // sqrt not defined for integers
1868+
Value::Float(f) => Value::Float(f.sqrt()),
1869+
Value::Extension(data) if data.first() == Some(&(DataType::DeterministicFloat as u8)) => {
1870+
// DFP sqrt via dfp_sqrt()
1871+
...
1872+
}
1873+
_ => Value::Null(DataType::Null),
1874+
};
1875+
self.stack.push(result);
1876+
}
1877+
```
1878+
1879+
### A5. Single Casting Truth (Resolves: F3)
1880+
1881+
Three code paths previously implemented independent type coercion:
1882+
1. `Value::cast_to_type(&self, target)` — borrowing
1883+
2. `Value::into_coerce_to_type(self, target)` — consuming
1884+
3. `CastExpr::perform_cast(&self, value)` — storage expression
1885+
1886+
**Requirement:** `perform_cast()` MUST delegate to `Value::cast_to_type()`. `into_coerce_to_type()` MUST use `cast_to_type()` internally, only inlining the same-type fast-path (no conversion needed, return self).
1887+
1888+
### A6. DETERMINISTIC VIEW (Resolves: S10)
1889+
1890+
Deferred to a separate RFC-0110. The VM's `deterministic` flag and `arithmetic_op_deterministic()` method are reserved infrastructure. The SQL surface (`CREATE DETERMINISTIC VIEW`) requires parser grammar changes beyond the scope of this amendment.
1891+
1892+
### A7. Verification Requirements
1893+
1894+
DFP integration MUST include:
1895+
1896+
| Category | Tests Required | Coverage |
1897+
|----------|---------------|----------|
1898+
| Value API | Round-trip, Display, as_string, as_float64, coercion | Per type conversion |
1899+
| VM Arithmetic | add/sub/mul/div/mod/neg/abs/cmp | Per opcode × per type |
1900+
| Cross-type comparison | DFP vs Int, DFP vs Float | Per combination |
1901+
| SQL round-trip | CREATE → INSERT → SELECT → WHERE → UPDATE → DELETE | End-to-end |
1902+
| Persistence | Serialization → deserialization fidelity | Per wire format |
1903+
1904+
---
1905+
1906+
**Version:** 1.17
18151907
**Submission Date:** 2025-03-06
18161908
**Last Updated:** 2026-03-08
18171909
**Changes:** v1.16 final fixes (10/10):

rfcs/accepted/numeric/0105-deterministic-quant-arithmetic.md

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1178,8 +1178,95 @@ This invariant ensures:
11781178

11791179
---
11801180

1181+
## Appendix: Implementation Architecture Amendment (v2.14)
1182+
1183+
> **Date:** 2026-04-01
1184+
> **Status:** Accepted amendment based on code review findings
1185+
1186+
### B1. Unified Runtime Dispatch (Resolves: S8)
1187+
1188+
The original RFC specified 7 type-specific DQA opcodes (`OP_DQA_ADD/SUB/MUL/DIV/NEG/ABS/CMP`). After implementation review, the architecture uses **unified runtime dispatch** as the primary path:
1189+
1190+
- All generic `Op::Add/Sub/Mul/Div/Mod/Neg/Abs` route through a single `arithmetic_op()` method
1191+
- `arithmetic_op()` performs runtime type detection via `is_quant_value()` / `is_dfp()` byte checks
1192+
- DQA-specific opcodes (`Op::DqaAdd`, etc.) are **reserved for future JIT optimization** — dispatched correctly in the VM main loop but never emitted by the current compiler
1193+
1194+
**Rationale:** The 7 DQA opcodes are fully implemented and tested but the compiler emits only generic opcodes. Making the compiler type-aware would require schema inspection during compilation — a significant architectural change deferred to a future optimization pass.
1195+
1196+
**Compiler requirement:** The expression compiler MUST ensure all generic arithmetic opcodes route through `arithmetic_op()`. Any bypass (like the old `div_op()`/`mod_op()` static methods) risks silently returning NULL for Extension types.
1197+
1198+
### B2. Cross-Type Numeric Comparison (Resolves: S1, S10)
1199+
1200+
**Previous behavior:** Cross-type comparison used `as_float64().unwrap()`, which:
1201+
1. Returned `None` for all Extension types (including Quant), causing server panics
1202+
2. For DQA: `(q.value as f64) / 10^q.scale` overflows f64 for large values with high scale
1203+
1204+
**New behavior:** Cross-type comparison uses type promotion:
1205+
1206+
| Left Type | Right Type | Comparison Strategy |
1207+
|-----------|------------|---------------------|
1208+
| Quant | Integer | Promote Integer → Quant(scale=0), compare via `dqa_cmp` |
1209+
| Quant | Float | Convert Quant → f64 (lossy but explicit), compare as f64 |
1210+
| Quant | DFP | `Error::IncomparableTypes` (explicit CAST required) |
1211+
| Quant | Quant | Same-type `dqa_cmp` after canonicalization |
1212+
1213+
**Warning:** Quant → f64 conversion for comparison is lossy for values exceeding f64 integer precision (2^53) at high scales. A query like `WHERE quant_col > 9007199254740993` may produce incorrect results for values near the precision boundary. This is documented as a known limitation — use `CAST(float_col AS DQA)` for exact comparison.
1214+
1215+
### B3. Single Casting Truth (Resolves: S2, S3, S4, S7)
1216+
1217+
Three code paths previously implemented independent type coercion for Quant:
1218+
1. `Value::cast_to_type()` — borrowing, was a stub returning NULL
1219+
2. `Value::into_coerce_to_type()` — consuming, was a stub returning NULL
1220+
3. `CastExpr::perform_cast()` — was returning Error
1221+
1222+
**Requirement:** All three paths delegate to a single implementation in `Value::cast_to_type()`:
1223+
- `into_coerce_to_type()` calls `cast_to_type()` internally, inlining only the same-type fast-path
1224+
- `perform_cast()` delegates to `Value::cast_to_type()` via `Ok(value.cast_to_type(target))`
1225+
1226+
### B4. Validation on Extraction (Resolves: S9)
1227+
1228+
`extract_dqa_from_extension()` previously used `Some(Dqa { value, scale })` (direct construction), bypassing `Dqa::new()` validation. A corrupted payload with `scale > 18` would be accepted.
1229+
1230+
**Requirement:** All DQA extraction from byte data MUST use `Dqa::new(value, scale).ok()` or equivalent validation. Direct `Dqa { value, scale }` construction is only permitted in `Dqa::new()` itself and in the `CANONICAL_ZERO` constant.
1231+
1232+
### B5. Persistence Validation (Resolves: S13)
1233+
1234+
Wire tag 11 (generic extension) deserialization now validates DQA payloads:
1235+
1236+
```
1237+
if dt == DataType::Quant && len != 16:
1238+
return Err(Error::internal("corrupted DQA extension: expected 16 bytes, got {len}"));
1239+
```
1240+
1241+
DFP payloads are also validated (expected 24 bytes for `DfpEncoding`).
1242+
1243+
### B6. Display and String Representation (Resolves: S5, S10)
1244+
1245+
Quant values display as their decimal representation:
1246+
- `Dqa { value: 123, scale: 2 }``"1.23"`
1247+
- `Dqa { value: 42, scale: 0 }``"42"`
1248+
- `Dqa { value: -100, scale: 2 }``"-1"`
1249+
1250+
The `format_dqa()` and `parse_string_to_dqa()` helpers handle the bidirectional conversion.
1251+
1252+
### B7. Verification Requirements
1253+
1254+
DQA integration MUST include:
1255+
1256+
| Category | Tests Required | Coverage |
1257+
|----------|---------------|----------|
1258+
| Value API | Round-trip, Display, as_string, as_float64, coercion | Per type conversion |
1259+
| VM Arithmetic | add/sub/mul/div/mod (via arithmetic_op_quant) | Per operation |
1260+
| Cross-type comparison | Quant vs Int, Quant vs Float | Per combination |
1261+
| Format/parse | format_dqa ↔ parse_string_to_dqa round-trip | Scale 0, 1, 2, 9, 18 |
1262+
| SQL round-trip | CREATE → INSERT → SELECT → WHERE | End-to-end |
1263+
| Persistence | Serialization → deserialization with validation | Corrupted payloads rejected |
1264+
1265+
---
1266+
11811267
**Submission Date:** 2025-03-06
1182-
**Last Updated:** 2026-03-08
1268+
**Last Updated:** 2026-04-01
1269+
**Revision:** v2.14 - Architecture amendment: unified runtime dispatch, cross-type comparison, single casting truth, validation on extraction, persistence validation, display/string representation, verification requirements
11831270
**Revision:** v2.13 - Tightened MUL clamping wording, added large-value chain test, added >90% note to DQA_CMP fast-path
11841271
**Revision:** v2.12 - Added SQL vs canonical representation clarification, fixed division rounding wording (TARGET_SCALE precision), strengthened SIMD determinism rule, enforced canonicalization in encoding API, added control-flow to VM canonicalization rule, added power<=36 invariant, added scale alignment overflow test vector
11851272
**Revision:** v2.11 - Fixed DIV negative test vector (-12 not -13), added i64 range check to DQA_ASSIGN_TO_COLUMN, added CANONICALIZE to DIV return, unified scale overflow references, fixed test vector notes, added DIV canonicalization test vector, fixed MAX_I128_DIGITS to 39

0 commit comments

Comments
 (0)