prism_numerics/
fixed_point.rs1#![allow(missing_docs)]
13
14use uor_foundation::enforcement::{GroundedShape, ShapeViolation};
15use uor_foundation::pipeline::{ConstrainedTypeShape, ConstraintRef, IntoBindingValue};
16use uor_foundation_sdk::axis;
17
18use crate::{check_output, split_pair};
19
20axis! {
21 pub trait FixedPointAxis: AxisExtension {
28 const AXIS_ADDRESS: &'static str = "https://uor.foundation/axis/FixedPointAxis";
30 const MAX_OUTPUT_BYTES: usize = 8;
32 fn add(input: &[u8], out: &mut [u8]) -> Result<usize, ShapeViolation>;
38 fn sub(input: &[u8], out: &mut [u8]) -> Result<usize, ShapeViolation>;
44 fn mul(input: &[u8], out: &mut [u8]) -> Result<usize, ShapeViolation>;
51 }
52}
53
54const WIDTH: usize = 8;
55
56fn format_violation() -> ShapeViolation {
57 ShapeViolation {
58 shape_iri: "https://uor.foundation/axis/FixedPointAxis",
59 constraint_iri: "https://uor.foundation/axis/FixedPointAxis/iPlusFInRange",
60 property_iri: "https://uor.foundation/axis/qFormatTotalBits",
61 expected_range: "https://uor.foundation/axis/FixedPointAxis/I64Fit",
62 min_count: 1,
63 max_count: 64,
64 kind: uor_foundation::ViolationKind::ValueCheck,
65 }
66}
67
68fn decode(slice: &[u8]) -> i64 {
69 let mut buf = [0u8; 8];
70 buf.copy_from_slice(&slice[..8]);
71 i64::from_be_bytes(buf)
72}
73
74fn encode(value: i64) -> [u8; 8] {
75 value.to_be_bytes()
76}
77
78#[derive(Debug, Clone, Copy)]
85pub struct FixedPointQNumeric<const INT_BITS: u32, const FRAC_BITS: u32>;
86
87impl<const I: u32, const F: u32> Default for FixedPointQNumeric<I, F> {
88 fn default() -> Self {
89 Self
90 }
91}
92
93impl<const INT_BITS: u32, const FRAC_BITS: u32> FixedPointAxis
94 for FixedPointQNumeric<INT_BITS, FRAC_BITS>
95{
96 const AXIS_ADDRESS: &'static str = "https://uor.foundation/axis/FixedPointAxis/Q";
97 const MAX_OUTPUT_BYTES: usize = WIDTH;
98
99 fn add(input: &[u8], out: &mut [u8]) -> Result<usize, ShapeViolation> {
100 if INT_BITS + FRAC_BITS == 0 || INT_BITS + FRAC_BITS > 64 {
101 return Err(format_violation());
102 }
103 let (a, b) = split_pair(input, WIDTH)?;
104 check_output(out, WIDTH)?;
105 let result = decode(a).saturating_add(decode(b));
106 out[..WIDTH].copy_from_slice(&encode(result));
107 Ok(WIDTH)
108 }
109
110 fn sub(input: &[u8], out: &mut [u8]) -> Result<usize, ShapeViolation> {
111 if INT_BITS + FRAC_BITS == 0 || INT_BITS + FRAC_BITS > 64 {
112 return Err(format_violation());
113 }
114 let (a, b) = split_pair(input, WIDTH)?;
115 check_output(out, WIDTH)?;
116 let result = decode(a).saturating_sub(decode(b));
117 out[..WIDTH].copy_from_slice(&encode(result));
118 Ok(WIDTH)
119 }
120
121 fn mul(input: &[u8], out: &mut [u8]) -> Result<usize, ShapeViolation> {
122 if INT_BITS + FRAC_BITS == 0 || INT_BITS + FRAC_BITS > 64 {
123 return Err(format_violation());
124 }
125 let (a, b) = split_pair(input, WIDTH)?;
126 check_output(out, WIDTH)?;
127 let product = i128::from(decode(a)) * i128::from(decode(b));
128 let rescaled = product >> FRAC_BITS;
129 let saturated: i64 = if rescaled > i128::from(i64::MAX) {
130 i64::MAX
131 } else if rescaled < i128::from(i64::MIN) {
132 i64::MIN
133 } else {
134 #[allow(clippy::cast_possible_truncation)]
135 {
136 rescaled as i64
137 }
138 };
139 out[..WIDTH].copy_from_slice(&encode(saturated));
140 Ok(WIDTH)
141 }
142}
143
144axis_extension_impl_for_fixed_point_axis!(
147 @generic FixedPointQNumeric<INT_BITS, FRAC_BITS>,
148 [const INT_BITS: u32, const FRAC_BITS: u32]
149);
150
151pub type FixedPointQ32_32Numeric = FixedPointQNumeric<32, 32>;
153pub type FixedPointQ16_16Numeric = FixedPointQNumeric<16, 16>;
155pub type FixedPointQ1_31Numeric = FixedPointQNumeric<1, 31>;
157pub type FixedPointQ48_16Numeric = FixedPointQNumeric<48, 16>;
159
160#[derive(Debug, Clone, Copy)]
170pub struct FixedPointShape<const INT_BITS: u32, const FRAC_BITS: u32>;
171
172impl<const I: u32, const F: u32> Default for FixedPointShape<I, F> {
173 fn default() -> Self {
174 Self
175 }
176}
177
178impl<const INT_BITS: u32, const FRAC_BITS: u32> ConstrainedTypeShape
179 for FixedPointShape<INT_BITS, FRAC_BITS>
180{
181 const IRI: &'static str = "https://uor.foundation/type/ConstrainedType";
182 const SITE_COUNT: usize = WIDTH;
183 const CONSTRAINTS: &'static [ConstraintRef] = &[];
184 #[allow(clippy::cast_possible_truncation)]
185 const CYCLE_SIZE: u64 = 256u64.saturating_pow(WIDTH as u32);
186}
187
188impl<const INT_BITS: u32, const FRAC_BITS: u32> uor_foundation::pipeline::__sdk_seal::Sealed
189 for FixedPointShape<INT_BITS, FRAC_BITS>
190{
191}
192impl<const INT_BITS: u32, const FRAC_BITS: u32> GroundedShape
193 for FixedPointShape<INT_BITS, FRAC_BITS>
194{
195}
196impl<const INT_BITS: u32, const FRAC_BITS: u32> IntoBindingValue
197 for FixedPointShape<INT_BITS, FRAC_BITS>
198{
199 const MAX_BYTES: usize = WIDTH;
200
201 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
202 Ok(0)
203 }
204}