1use crate::cast::Canon;
9use crate::{Array, Category, Complex64, DType, Error, Result, bf16, f16};
10
11#[derive(Debug, Clone, Copy, PartialEq)]
13pub enum Scalar {
14 Bool(bool),
16 Int(i128),
18 Float(f64),
20 Complex(Complex64),
22}
23
24#[derive(Debug, Clone)]
26pub enum ScalarOrArray {
27 Array(Array),
29 Scalar(Scalar),
31}
32
33impl From<Array> for ScalarOrArray {
34 fn from(a: Array) -> Self {
35 Self::Array(a)
36 }
37}
38
39impl From<&Array> for ScalarOrArray {
40 fn from(a: &Array) -> Self {
41 Self::Array(a.clone())
42 }
43}
44
45impl From<Scalar> for ScalarOrArray {
46 fn from(s: Scalar) -> Self {
47 Self::Scalar(s)
48 }
49}
50
51pub trait ScalarOperand: Into<Scalar> {}
57
58macro_rules! from_scalar {
59 ($($t:ty => |$x:ident| $e:expr),* $(,)?) => {$(
60 impl ScalarOperand for $t {}
61 impl From<$t> for Scalar {
62 fn from($x: $t) -> Self {
63 $e
64 }
65 }
66 impl From<$t> for ScalarOrArray {
67 fn from(x: $t) -> Self {
68 Self::Scalar(Scalar::from(x))
69 }
70 }
71 )*};
72}
73
74from_scalar! {
75 bool => |x| Scalar::Bool(x),
76 i8 => |x| Scalar::Int(x.into()),
77 i16 => |x| Scalar::Int(x.into()),
78 i32 => |x| Scalar::Int(x.into()),
79 i64 => |x| Scalar::Int(x.into()),
80 u8 => |x| Scalar::Int(x.into()),
81 u16 => |x| Scalar::Int(x.into()),
82 u32 => |x| Scalar::Int(x.into()),
83 u64 => |x| Scalar::Int(x.into()),
84 f16 => |x| Scalar::Float(x.to_f64()),
85 bf16 => |x| Scalar::Float(x.to_f64()),
86 f32 => |x| Scalar::Float(x.into()),
87 f64 => |x| Scalar::Float(x),
88 Complex64 => |x| Scalar::Complex(x),
89}
90
91fn int_range(d: DType) -> (i128, i128) {
93 match d {
94 DType::U8 => (0, u8::MAX.into()),
95 DType::U16 => (0, u16::MAX.into()),
96 DType::U32 => (0, u32::MAX.into()),
97 DType::U64 => (0, u64::MAX.into()),
98 DType::I8 => (i8::MIN.into(), i8::MAX.into()),
99 DType::I16 => (i16::MIN.into(), i16::MAX.into()),
100 DType::I32 => (i32::MIN.into(), i32::MAX.into()),
101 _ => (i64::MIN.into(), i64::MAX.into()),
102 }
103}
104
105impl Scalar {
106 pub fn default_dtype(self) -> DType {
109 match self {
110 Self::Bool(_) => DType::Bool,
111 Self::Int(v) if i32::try_from(v).is_ok() => DType::I32,
112 Self::Int(v) if i64::try_from(v).is_ok() => DType::I64,
113 Self::Int(_) => DType::U64,
114 Self::Float(_) => DType::F32,
115 Self::Complex(_) => DType::Complex64,
116 }
117 }
118
119 fn dtype_like(self, like: DType) -> Result<DType> {
121 Ok(match self {
122 Self::Bool(_) => like,
123 Self::Int(v) => match like.category() {
124 Category::Bool => Self::Int(v).default_dtype(),
126 Category::Signed | Category::Unsigned => {
127 let (lo, hi) = int_range(like);
128 if !(lo..=hi).contains(&v) {
129 return Err(Error::InvalidArgument(format!(
130 "converting {v} to {like} would overflow"
131 )));
132 }
133 like
134 }
135 _ => like,
136 },
137 Self::Float(_) if like.category() == Category::Float => like,
138 Self::Float(_) => DType::F32,
139 Self::Complex(_) => DType::Complex64,
140 })
141 }
142
143 pub(crate) fn canon(self) -> Canon {
144 match self {
145 Self::Bool(b) => Canon::Int(i128::from(b)),
146 Self::Int(v) => Canon::Int(v),
147 Self::Float(f) => Canon::Float(f),
148 Self::Complex(z) => Canon::Complex(z),
149 }
150 }
151
152 pub(crate) fn to_array(self, like: Option<DType>) -> Result<Array> {
154 let dtype = match like {
155 Some(like) => self.dtype_like(like)?,
156 None => self.default_dtype(),
157 };
158 crate::ops::imp::filled(&[], dtype, |_| self.canon())
159 }
160}
161
162impl ScalarOrArray {
163 pub(crate) fn into_array(self, like: Option<DType>) -> Result<Array> {
165 match self {
166 Self::Array(a) => Ok(a),
167 Self::Scalar(s) => s.to_array(like),
168 }
169 }
170
171 fn dtype(&self) -> Option<DType> {
172 match self {
173 Self::Array(a) => Some(a.dtype()),
174 Self::Scalar(_) => None,
175 }
176 }
177}
178
179pub(crate) fn resolve(a: ScalarOrArray, b: ScalarOrArray) -> Result<(Array, Array)> {
183 for x in [&a, &b] {
184 if let ScalarOrArray::Array(x) = x
185 && let Some(err) = x.slot_error()
186 {
187 return Err(err);
188 }
189 }
190 let (da, db) = (a.dtype(), b.dtype());
191 Ok((a.into_array(db)?, b.into_array(da)?))
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 fn like(s: impl Into<Scalar>, d: DType) -> Result<DType> {
199 s.into().dtype_like(d)
200 }
201
202 #[test]
203 fn mlx_weak_typing_rules() {
204 assert_eq!(like(2.0, DType::F16).unwrap(), DType::F16);
205 assert_eq!(like(2.0, DType::I32).unwrap(), DType::F32);
206 assert_eq!(like(2.0, DType::F64).unwrap(), DType::F64);
207 assert_eq!(like(2, DType::I8).unwrap(), DType::I8);
208 assert_eq!(like(2, DType::F16).unwrap(), DType::F16);
209 assert_eq!(like(2, DType::Bool).unwrap(), DType::I32);
210 assert_eq!(like(true, DType::U16).unwrap(), DType::U16);
211 assert_eq!(
212 like(Complex64::new(1.0, 1.0), DType::F32).unwrap(),
213 DType::Complex64
214 );
215 assert!(matches!(
216 like(300, DType::I8),
217 Err(Error::InvalidArgument(_))
218 ));
219 assert!(matches!(
220 like(-1, DType::U32),
221 Err(Error::InvalidArgument(_))
222 ));
223 assert_eq!(like(u64::MAX, DType::U64).unwrap(), DType::U64);
224 }
225
226 #[test]
227 fn defaults() {
228 assert_eq!(Scalar::from(1).default_dtype(), DType::I32);
229 assert_eq!(Scalar::from(1i64 << 40).default_dtype(), DType::I64);
230 assert_eq!(Scalar::from(u64::MAX).default_dtype(), DType::U64);
231 assert_eq!(Scalar::from(1.0f32).default_dtype(), DType::F32);
232 assert_eq!(Scalar::from(false).default_dtype(), DType::Bool);
233 }
234}