1use crate::float::{Float, sealed::{Core, Sealed}};
3use num_bigint::{BigInt, Sign};
4use num_traits::Bounded;
5
6pub trait Int: Copy + Sealed + sealed::Ways {}
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
10pub enum ConversionKind { Cast, Truncate, Bits, Keep }
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14pub struct Conversion { pub input: crate::buffer::DType, pub output: crate::buffer::DType, pub kind: ConversionKind }
15impl Conversion {
16 pub fn validity<F: crate::Float, O: crate::recipe::Ops<F> + ?Sized>(self, o: &O, x: O::V) -> Option<(crate::error::Kind, O::B)> {
17 use crate::buffer::{DType, Width};
18 if self.kind == ConversionKind::Keep && self.input.is_integral() && self.output.is_integral() {
19 let bounds = |dtype| match dtype {
20 DType::Boolean => (0, 1), DType::Integer(width) => with_width!(width, T => (T::MIN as i64, T::MAX as i64)), _ => unreachable!()
21 };
22 let ((input_lo, input_hi), (lo, hi)) = (bounds(self.input), bounds(self.output));
23 if lo <= input_lo && hi >= input_hi { return None; }
24 let x = o.to_integer(x);
25 let valid = o.and(
26 o.integer_compare(crate::recipe::Cmp::LessEqual, o.integer(lo.max(input_lo)), x),
27 o.integer_compare(crate::recipe::Cmp::LessEqual, x, o.integer(hi.min(input_hi))));
28 return Some((crate::error::Kind::Domain, valid));
29 }
30 if self.kind != ConversionKind::Cast { return None; }
31 let valid = match self.output {
32 DType::Boolean => o.or(o.equal(x, o.number(F::zero())), o.equal(x, o.number(F::one()))),
33 DType::Integer(width) if matches!(self.input, DType::Float(_)) => {
34 let upper = if width == Width::U8 { 256.0 } else { 2f64.powi((8 * self.output.bytes() - 1) as i32) };
35 let lower = if width == Width::U8 { 0.0 } else { -upper };
36 let x = if width == Width::I64 { x } else { o.from_integer(o.to_integer(x)) };
37 o.and(o.less_equal(o.number(F::narrow(lower)), x), o.less(x, o.number(F::narrow(upper))))
38 }
39 _ => return None,
40 };
41 Some((crate::error::Kind::Overflow, valid))
42 }
43 pub fn may_raise(self, checked: bool) -> bool {
44 self.kind == ConversionKind::Keep || checked && (self.output == crate::buffer::DType::Character || self
45 .validity::<f64, _>(&crate::recipe::RangeOps(self.input), crate::domain::Range::of_dtype(self.input))
46 .is_some_and(|(_, valid)| valid != Some(true)))
47 }
48 pub fn map(
49 self, input: crate::agreement::Read<'_>, range: std::ops::Range<usize>, checked: bool, output: &mut crate::buffer::Buffer
50 ) -> Result<(), crate::error::Error> {
51 use crate::buffer::{Buffer, DType, FloatWidth, Floats};
52 use crate::error::{Error, Kind};
53 if self.kind == ConversionKind::Cast && !matches!((self.input, self.output), (DType::Integer(_), DType::Integer(_))) {
54 return input.read_into(range, self.output, checked, output);
55 }
56 let mut scratch = Buffer::with_capacity(self.input, 0);
57 let values = input.part_view(range, self.input, checked, &mut scratch)?;
58 *output = match (self.kind, values, self.output) {
59 (ConversionKind::Cast, _, _) => Buffer::cast_results(values, self.output, checked)?,
60 (ConversionKind::Keep, _, _) => Buffer::kept(values, self.output)?,
61 (ConversionKind::Truncate, crate::buffer::View::Floats(_), DType::Integer(_)) => Buffer::cast_results(values, self.output, false)?,
62 (ConversionKind::Bits, crate::buffer::View::Floats(Floats::F32(values)), DType::Integer(crate::buffer::Width::I64)) => {
63 Buffer::Integers(crate::buffer::IntBuf::I64(values.iter().map(|x| i64::from(x.to_bits())).collect()))
64 }
65 (ConversionKind::Bits, crate::buffer::View::Floats(Floats::F64(values)), DType::Integer(crate::buffer::Width::I64)) => {
66 Buffer::Integers(crate::buffer::IntBuf::I64(values.iter().map(|x| x.to_bits() as i64).collect()))
67 }
68 (ConversionKind::Bits, crate::buffer::View::Integers(values), DType::Float(FloatWidth::F32)) => {
69 Buffer::Floats(crate::buffer::FloatBuf::F32((0..values.len()).map(|i| f32::from_bits(values.get(i) as u32)).collect()))
70 }
71 (ConversionKind::Bits, crate::buffer::View::Integers(values), DType::Float(FloatWidth::F64)) => {
72 Buffer::Floats(crate::buffer::FloatBuf::F64((0..values.len()).map(|i| f64::from_bits(values.get(i) as u64)).collect()))
73 }
74 (ConversionKind::Truncate, _, _) if self.input.is_integral() && self.output.is_integral() => Buffer::cast_results(values, self.output, false)?,
75 _ => return Err(Error::new(Kind::Domain, "unsupported resolved conversion")),
76 };
77 Ok(())
78 }
79}
80
81pub(crate) mod sealed {
82 pub trait Ways: Copy {
84 fn from_f32(x: f32) -> Option<Self>;
85 fn from_f64(x: f64) -> Option<Self>;
86 fn saturated_f32(x: f32) -> Self;
87 fn saturated_f64(x: f64) -> Self;
88 fn to_f32(self) -> f32;
89 fn to_f64(self) -> f64;
90 }
91}
92
93#[inline(always)]
95pub fn int<I: Int, F: Float>(x: F) -> Option<I> { if <F::Working as Core>::WIDE { I::from_f64(x.wide()) } else { I::from_f32(x.wide() as f32) } }
96
97#[inline(always)]
99pub fn saturated<I, S: Saturates<I>>(x: S) -> I { x.saturated() }
100pub trait Saturates<I> { fn saturated(self) -> I; }
102impl<I: Int, F: Float> Saturates<I> for F {
103 #[inline(always)]
104 fn saturated(self) -> I { if <F::Working as Core>::WIDE { I::saturated_f64(self.wide()) } else { I::saturated_f32(self.wide() as f32) } }
105}
106impl<I: Bounded + for<'a> TryFrom<&'a BigInt>> Saturates<I> for &BigInt {
107 fn saturated(self) -> I { I::try_from(self).unwrap_or_else(|_| if self.sign() == Sign::Minus { I::min_value() } else { I::max_value() }) }
108}
109impl<I: Bounded + TryFrom<u64>> Saturates<I> for u64 { fn saturated(self) -> I { I::try_from(self).unwrap_or_else(|_| I::max_value()) } }
110#[inline(always)]
112pub fn floored<I: Int, F: Float>(x: F, t: impl crate::tolerant::Tolerant) -> I { saturated(t.floor(x)) }
113
114#[inline(always)]
116pub fn float<F: Float, I: Int>(i: I) -> F { if <F::Working as Core>::WIDE { F::narrow(i.to_f64()) } else { F::narrow(i.to_f32().into()) } }
117
118pub fn count<F: Float>(out: &mut Vec<F>, start: i64, down: bool, n: usize) {
120 let end = if down { start - n as i64 } else { start + n as i64 };
121 if !<F::Working as Core>::WIDE && i32::try_from(start).is_ok() && i32::try_from(end).is_ok() {
122 let start = start as i32;
123 return if down { out.extend((0..n as i32).map(move |i| float::<F, i32>(start - i))) }
124 else { out.extend((0..n as i32).map(move |i| float::<F, i32>(start + i))) };
125 }
126 if NATIVE_64 {
127 return if down { out.extend((0..n).map(move |i| F::narrow(wide_float(start - i as i64)))) }
128 else { out.extend((0..n).map(move |i| F::narrow(wide_float(start + i as i64)))) };
129 }
130 let step = if down { -1 } else { 1 };
131 let offsets: [f64; 8] = std::array::from_fn(|k| wide_float(step * k as i64));
132 let first = move |j: usize| wide_float(start + step * (8 * j) as i64);
133 unsafe {
134 crate::slice::appended(out, n, |slots| {
135 let (blocks, rest) = slots.as_chunks_mut::<8>();
136 let whole = blocks.len();
137 for (j, block) in blocks.iter_mut().enumerate() { let first = first(j); for k in 0..8 { block[k].write(F::narrow(first + offsets[k])); } }
138 for (slot, &offset) in rest.iter_mut().zip(&offsets) { slot.write(F::narrow(first(whole) + offset)); }
139 })
140 };
141}
142
143const NATIVE_64: bool = cfg!(any(target_arch = "aarch64", target_feature = "avx512dq"));
145
146const SATURATING: bool = !cfg!(target_arch = "x86_64");
148
149pub(crate) const WIDE_LIMIT: f64 = 2251799813685247.0;
151
152#[inline(always)]
154fn wide_float(i: i64) -> f64 { if NATIVE_64 { i as f64 } else { halves(i) } }
155
156#[inline(always)]
158fn halves(i: i64) -> f64 {
159 let high = f64::from_bits(0x4530_0000_0000_0000 | ((i as u64 >> 32) ^ 0x8000_0000)) - f64::from_bits(0x4530_0000_8000_0000);
160 high + f64::from_low(i as u64 & 0xffff_ffff, 0.0)
161}
162
163#[inline(always)]
165pub(crate) fn shifted(x: f64) -> i64 { (x + f64::SHIFT).to_bits().wrapping_sub(f64::SHIFT.to_bits()) as i64 }
166
167#[inline(always)]
169pub(crate) fn unshifted(i: i64) -> f64 { f64::from_bits(f64::SHIFT.to_bits().wrapping_add(i as u64)) - f64::SHIFT }
170
171macro_rules! ints {
173 ($($t:ty: $kind:ident, |$i:ident| $f32:expr, $f64:expr;)+) => {$(
174 impl Int for $t {}
175 impl sealed::Ways for $t {
176 #[inline(always)]
177 fn from_f32(x: f32) -> Option<$t> { ints!(@$kind f32, $t, x) }
178 #[inline(always)]
179 fn from_f64(x: f64) -> Option<$t> { ints!(@$kind f64, $t, x) }
180 #[inline(always)]
181 fn saturated_f32(x: f32) -> $t { ints!(@saturated $kind f32, $t, x) }
182 #[inline(always)]
183 fn saturated_f64(x: f64) -> $t { ints!(@saturated $kind f64, $t, x) }
184 #[inline(always)]
185 fn to_f32(self) -> f32 { let $i = self; $f32 }
186 #[inline(always)]
187 fn to_f64(self) -> f64 { let $i = self; $f64 }
188 }
189 )+};
190 (@narrow $f:ty, $t:ty, $x:ident) => {{
191 let (mid, half) = ((<$t>::MIN as f64 + <$t>::MAX as f64) / 2.0, (<$t>::MAX as f64 - <$t>::MIN as f64 + 1.0) / 2.0);
193 (($x - mid as $f).abs() < half as $f).then(|| {
194 if SATURATING { return $x as i32 as $t }
195 unsafe { $x.to_int_unchecked::<i32>() as $t }
197 })
198 }};
199 (@wide f32, $t:ty, $x:ident) => { Self::from_f64($x.into()) };
200 (@wide f64, $t:ty, $x:ident) => {{
201 let n = if NATIVE_64 { $x as i64 } else { shifted($x) };
202 ($x.abs() <= WIDE_LIMIT).then_some(n).and_then(|n| <$t>::try_from(n).ok())
203 }};
204 (@saturated narrow f32, $t:ty, $x:ident) => { if SATURATING { $x as $t } else { ints!(@clamped f32, $t, $x) } };
205 (@saturated narrow f64, $t:ty, $x:ident) => { if NATIVE_64 { ($x as i64).clamp(<$t>::MIN.into(), <$t>::MAX.into()) as $t } else { ints!(@clamped f64, $t, $x) } };
207 (@saturated wide $f:ty, $t:ty, $x:ident) => { $x as $t };
208 (@clamped $f:ty, $t:ty, $x:ident) => {{
209 let top = <$t>::MAX as $f;
211 let below = if top as i128 > <$t>::MAX as i128 { top.next_down() } else { top };
212 let c = if $x.is_nan() { 0.0 } else { $x.max(<$t>::MIN as $f).min(below) };
213 let i = unsafe { c.to_int_unchecked::<$t>() };
215 if $x >= top { <$t>::MAX } else { i }
216 }};
217}
218
219ints! {
221 i8: narrow, |i| i as f32, i as f64;
222 i16: narrow, |i| i as f32, i as f64;
223 i32: narrow, |i| i as f32, i as f64;
224 u8: narrow, |i| f32::from_low(i.into(), 0.0), f32::from_low(i.into(), 0.0).into();
225 u16: narrow, |i| f32::from_low(i.into(), 0.0), f32::from_low(i.into(), 0.0).into();
226 u32: wide, |i| f64::from_low(i.into(), 0.0) as f32, f64::from_low(i.into(), 0.0);
227 i64: wide, |i| wide_float(i) as f32, wide_float(i);
228}