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