Skip to main content

konjure_sdk/data/
scalar.rs

1use alloc::string::ToString;
2use core::{cmp::Ordering, fmt, str::FromStr};
3use rustc_apfloat::{
4    Float, FloatConvert, Round, Status, StatusAnd,
5    ieee::{Double, Half, Quad, Single},
6};
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8
9use super::DataError;
10
11macro_rules! dtypes {
12    ($($variant:ident, $name:literal, $bits:literal, $signed:literal, $float:literal, $doc:literal);+ $(;)?) => {
13        /// Declared numeric representation, independent of the host architecture.
14        #[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
15        #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
16        #[serde(rename_all = "lowercase")]
17        pub enum DType {
18            $(#[doc = $doc] $variant,)+
19        }
20
21        impl DType {
22            /// All supported numeric representations in signed, unsigned, float order.
23            pub const ALL: [Self; 14] = [$(Self::$variant,)+];
24
25            /// Source-language name and serialized discriminator.
26            pub const fn name(self) -> &'static str {
27                match self { $(Self::$variant => $name,)+ }
28            }
29
30            /// Representation contract used by generated reference documentation.
31            pub const fn documentation(self) -> &'static str {
32                match self { $(Self::$variant => concat!($doc, "\n\nInteger arithmetic checks overflow; floating-point arithmetic rounds each operation at its declared precision and rejects nonfinite results. Mixed representations require explicit conversion, which rejects loss of range or precision. Values serialize as decimal strings so JSON hosts retain the exact value."),)+ }
33            }
34
35            /// Number of bits in one packed element.
36            pub const fn bit_width(self) -> usize {
37                match self { $(Self::$variant => $bits,)+ }
38            }
39
40            /// Number of bytes in one packed element.
41            pub const fn byte_width(self) -> usize { self.bit_width() / 8 }
42
43            /// Number of bytes in one packed element.
44            pub const fn bytes(self) -> usize { self.byte_width() }
45
46            /// Whether this representation is a signed integer.
47            pub const fn is_signed(self) -> bool {
48                match self { $(Self::$variant => $signed,)+ }
49            }
50
51            /// Whether this representation follows IEEE binary floating-point semantics.
52            pub const fn is_float(self) -> bool {
53                match self { $(Self::$variant => $float,)+ }
54            }
55        }
56    };
57}
58
59dtypes! {
60    I8, "i8", 8, true, false, "Signed 8-bit integer, from -128 through 127; checked arithmetic.";
61    I16, "i16", 16, true, false, "Signed 16-bit integer, from -32768 through 32767; checked arithmetic.";
62    I32, "i32", 32, true, false, "Signed 32-bit integer, from -2147483648 through 2147483647; checked arithmetic.";
63    I64, "i64", 64, true, false, "Signed 64-bit integer, from -9223372036854775808 through 9223372036854775807; checked arithmetic.";
64    I128, "i128", 128, true, false, "Signed 128-bit integer, from -170141183460469231731687303715884105728 through 170141183460469231731687303715884105727; checked arithmetic.";
65    U8, "u8", 8, false, false, "Unsigned 8-bit integer, from 0 through 255; checked arithmetic.";
66    U16, "u16", 16, false, false, "Unsigned 16-bit integer, from 0 through 65535; checked arithmetic.";
67    U32, "u32", 32, false, false, "Unsigned 32-bit integer, from 0 through 4294967295; checked arithmetic.";
68    U64, "u64", 64, false, false, "Unsigned 64-bit integer, from 0 through 18446744073709551615; checked arithmetic.";
69    U128, "u128", 128, false, false, "Unsigned 128-bit integer, from 0 through 340282366920938463463374607431768211455; checked arithmetic.";
70    F16, "f16", 16, false, true, "IEEE binary16: 11 significand bits, normal exponents -14 through 15, subnormals through 2^-24. Finite values only; ties-to-even rounding.";
71    F32, "f32", 32, false, true, "IEEE binary32: 24 significand bits, normal exponents -126 through 127, subnormals through 2^-149. Finite values only; ties-to-even rounding.";
72    F64, "f64", 64, false, true, "IEEE binary64: 53 significand bits, normal exponents -1022 through 1023, subnormals through 2^-1074. Finite values only; ties-to-even rounding.";
73    F128, "f128", 128, false, true, "IEEE binary128: 113 significand bits, normal exponents -16382 through 16383, subnormals through 2^-16494. Portable software arithmetic retains all 128 bits. Finite values only; ties-to-even rounding.";
74}
75
76impl fmt::Display for DType {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        f.write_str(self.name())
79    }
80}
81
82impl FromStr for DType {
83    type Err = DataError;
84    fn from_str(text: &str) -> Result<Self, Self::Err> {
85        Self::ALL
86            .into_iter()
87            .find(|dtype| dtype.name() == text)
88            .ok_or(DataError::TypeMismatch)
89    }
90}
91
92/// Checked elementwise arithmetic shared by scalar and tensor evaluation.
93#[derive(Clone, Copy, Debug, PartialEq, Eq)]
94pub enum BinaryOp {
95    /// Addition.
96    Add,
97    /// Subtraction.
98    Sub,
99    /// Multiplication.
100    Mul,
101    /// Division; integers truncate toward zero.
102    Div,
103    /// Remainder with the dividend's sign, including floating-point `fmod`.
104    Rem,
105}
106
107/// One finite numeric value with an explicit declared representation.
108///
109/// The private bits are validated at construction. Integers never pass through
110/// floating point, and binary128 never passes through binary64. [`PartialEq`]
111/// compares numeric values of matching types, so positive and negative float
112/// zero compare equal. [`Self::bits`] preserves their distinct representations.
113#[derive(Clone, Copy, Debug)]
114pub struct Scalar {
115    dtype: DType,
116    bits: u128,
117}
118
119impl Scalar {
120    /// Parses decimal text directly at the requested precision.
121    ///
122    /// Float literals are rounded once with ties-to-even; unlike [`Self::convert`],
123    /// decimal parsing accepts normal representational rounding (for example 0.1).
124    /// Text may have a sign, decimal point, and decimal exponent for floats.
125    ///
126    /// # Errors
127    /// Rejects malformed or overlong text, out-of-range integers, and nonfinite floats.
128    pub fn parse(dtype: DType, text: &str) -> Result<Self, DataError> {
129        if text.is_empty() || text.len() > 4096 {
130            return Err(DataError::InvalidLiteral);
131        }
132        if dtype.is_float() {
133            validate_decimal(text)?;
134            let bits = match dtype {
135                DType::F16 => parse_float::<Half>(text)?,
136                DType::F32 => parse_float::<Single>(text)?,
137                DType::F64 => parse_float::<Double>(text)?,
138                DType::F128 => parse_float::<Quad>(text)?,
139                _ => return Err(DataError::TypeMismatch),
140            };
141            Self::from_bits(dtype, bits)
142        } else if dtype.is_signed() {
143            let value = text.parse::<i128>().map_err(|error| match error.kind() {
144                core::num::IntErrorKind::PosOverflow | core::num::IntErrorKind::NegOverflow => {
145                    DataError::Overflow
146                }
147                _ => DataError::InvalidLiteral,
148            })?;
149            Self::from_signed(dtype, value)
150        } else {
151            let value = text.parse::<u128>().map_err(|error| match error.kind() {
152                core::num::IntErrorKind::PosOverflow | core::num::IntErrorKind::NegOverflow => {
153                    DataError::Overflow
154                }
155                _ => DataError::InvalidLiteral,
156            })?;
157            Self::from_unsigned(dtype, value)
158        }
159    }
160
161    /// Creates a finite value from unsigned representation bits.
162    ///
163    /// Signed integers use two's complement in exactly `dtype.bit_width()` bits;
164    /// high bits must be zero even for negative integers.
165    ///
166    /// # Errors
167    /// Rejects bits outside the declared width and nonfinite float encodings.
168    pub fn from_bits(dtype: DType, bits: u128) -> Result<Self, DataError> {
169        if bits & !mask(dtype) != 0 {
170            return Err(DataError::Overflow);
171        }
172        let finite = match dtype {
173            DType::F16 => Half::from_bits(bits).is_finite(),
174            DType::F32 => Single::from_bits(bits).is_finite(),
175            DType::F64 => Double::from_bits(bits).is_finite(),
176            DType::F128 => Quad::from_bits(bits).is_finite(),
177            _ => true,
178        };
179        if !finite {
180            return Err(DataError::NonFinite);
181        }
182        Ok(Self { dtype, bits })
183    }
184
185    /// Creates a signed integer after validating the declared range.
186    ///
187    /// # Errors
188    /// Rejects non-signed types and values outside their exact integer range.
189    pub fn from_signed(dtype: DType, value: i128) -> Result<Self, DataError> {
190        if !dtype.is_signed() {
191            return Err(DataError::TypeMismatch);
192        }
193        let shift = 128 - dtype.bit_width();
194        if value < (i128::MIN >> shift) || value > (i128::MAX >> shift) {
195            return Err(DataError::Overflow);
196        }
197        Ok(Self {
198            dtype,
199            bits: (value as u128) & mask(dtype),
200        })
201    }
202
203    /// Creates an unsigned integer after validating the declared range.
204    ///
205    /// # Errors
206    /// Rejects signed/float types and values outside the exact unsigned range.
207    pub fn from_unsigned(dtype: DType, value: u128) -> Result<Self, DataError> {
208        if dtype.is_signed() || dtype.is_float() {
209            return Err(DataError::TypeMismatch);
210        }
211        Self::from_bits(dtype, value)
212    }
213
214    /// Positive zero in any supported representation.
215    pub const fn zero(dtype: DType) -> Self {
216        Self { dtype, bits: 0 }
217    }
218
219    /// Declared numeric type.
220    pub const fn dtype(self) -> DType {
221        self.dtype
222    }
223
224    /// Representation bits with all unused high bits zero.
225    pub const fn bits(self) -> u128 {
226        self.bits
227    }
228
229    /// Signed integer value, without converting any other representation.
230    pub fn as_i128(self) -> Option<i128> {
231        self.dtype.is_signed().then(|| self.signed())
232    }
233
234    /// Unsigned integer value, without converting any other representation.
235    pub fn as_u128(self) -> Option<u128> {
236        (!self.dtype.is_signed() && !self.dtype.is_float()).then_some(self.bits)
237    }
238
239    /// Converts exactly to a native binary64 value for a host boundary.
240    ///
241    /// # Errors
242    /// Rejects any value not representable exactly in binary64.
243    pub fn to_f64(self) -> Result<f64, DataError> {
244        Ok(f64::from_bits(self.convert(DType::F64)?.bits as u64))
245    }
246
247    fn signed(self) -> i128 {
248        let shift = 128 - self.dtype.bit_width();
249        ((self.bits << shift) as i128) >> shift
250    }
251
252    /// Converts only when the exact value and signed-zero information survive.
253    ///
254    /// # Errors
255    /// Rejects out-of-range, fractional, or precision-losing conversions.
256    pub fn convert(self, dtype: DType) -> Result<Self, DataError> {
257        if self.dtype == dtype {
258            return Ok(self);
259        }
260        if self.dtype.is_float() {
261            return match self.dtype {
262                DType::F16 => convert_float(Half::from_bits(self.bits), dtype),
263                DType::F32 => convert_float(Single::from_bits(self.bits), dtype),
264                DType::F64 => convert_float(Double::from_bits(self.bits), dtype),
265                DType::F128 => convert_float(Quad::from_bits(self.bits), dtype),
266                _ => Err(DataError::TypeMismatch),
267            };
268        }
269        if dtype.is_float() {
270            let bits = match dtype {
271                DType::F16 => integer_to_float::<Half>(self)?,
272                DType::F32 => integer_to_float::<Single>(self)?,
273                DType::F64 => integer_to_float::<Double>(self)?,
274                DType::F128 => integer_to_float::<Quad>(self)?,
275                _ => return Err(DataError::TypeMismatch),
276            };
277            return Self::from_bits(dtype, bits);
278        }
279        if dtype.is_signed() {
280            let value = if self.dtype.is_signed() {
281                self.signed()
282            } else {
283                i128::try_from(self.bits).map_err(|_| DataError::Overflow)?
284            };
285            Self::from_signed(dtype, value)
286        } else {
287            let value = if self.dtype.is_signed() {
288                u128::try_from(self.signed()).map_err(|_| DataError::Overflow)?
289            } else {
290                self.bits
291            };
292            Self::from_unsigned(dtype, value)
293        }
294    }
295
296    /// Negates a value with checked integer overflow.
297    ///
298    /// # Errors
299    /// Rejects the signed minimum and nonzero unsigned values.
300    pub fn neg(&self) -> Result<Self, DataError> {
301        if self.dtype.is_float() {
302            return Self::from_bits(
303                self.dtype,
304                self.bits ^ (1_u128 << (self.dtype.bit_width() - 1)),
305            );
306        }
307        if self.dtype.is_signed() {
308            Self::from_signed(
309                self.dtype,
310                self.signed().checked_neg().ok_or(DataError::Overflow)?,
311            )
312        } else if self.bits == 0 {
313            Ok(*self)
314        } else {
315            Err(DataError::Overflow)
316        }
317    }
318
319    /// Evaluates one checked operation at the operands' declared precision.
320    ///
321    /// Floating-point rounding and underflow follow IEEE ties-to-even. Integer
322    /// overflow and nonfinite floating-point results are errors.
323    ///
324    /// # Errors
325    /// Rejects mixed types, zero divisors, overflow, and nonfinite results.
326    pub fn binary(self, operation: BinaryOp, rhs: Self) -> Result<Self, DataError> {
327        if self.dtype != rhs.dtype {
328            return Err(DataError::TypeMismatch);
329        }
330        if self.dtype.is_float() {
331            let bits = match self.dtype {
332                DType::F16 => float_binary::<Half>(self.bits, operation, rhs.bits)?,
333                DType::F32 => float_binary::<Single>(self.bits, operation, rhs.bits)?,
334                DType::F64 => float_binary::<Double>(self.bits, operation, rhs.bits)?,
335                DType::F128 => float_binary::<Quad>(self.bits, operation, rhs.bits)?,
336                _ => return Err(DataError::TypeMismatch),
337            };
338            return Self::from_bits(self.dtype, bits);
339        }
340        if matches!(operation, BinaryOp::Div | BinaryOp::Rem) && rhs.bits == 0 {
341            return Err(DataError::DivisionByZero);
342        }
343        if self.dtype.is_signed() {
344            let (left, right) = (self.signed(), rhs.signed());
345            let value = match operation {
346                BinaryOp::Add => left.checked_add(right),
347                BinaryOp::Sub => left.checked_sub(right),
348                BinaryOp::Mul => left.checked_mul(right),
349                BinaryOp::Div => left.checked_div(right),
350                BinaryOp::Rem => left.checked_rem(right),
351            }
352            .ok_or(DataError::Overflow)?;
353            // The signed minimum divided by -1 is invalid at every width,
354            // including remainder where the mathematical result would be zero.
355            if matches!(operation, BinaryOp::Div | BinaryOp::Rem)
356                && left == (i128::MIN >> (128 - self.dtype.bit_width()))
357                && right == -1
358            {
359                return Err(DataError::Overflow);
360            }
361            Self::from_signed(self.dtype, value)
362        } else {
363            let value = match operation {
364                BinaryOp::Add => self.bits.checked_add(rhs.bits),
365                BinaryOp::Sub => self.bits.checked_sub(rhs.bits),
366                BinaryOp::Mul => self.bits.checked_mul(rhs.bits),
367                BinaryOp::Div => self.bits.checked_div(rhs.bits),
368                BinaryOp::Rem => self.bits.checked_rem(rhs.bits),
369            }
370            .ok_or(DataError::Overflow)?;
371            Self::from_unsigned(self.dtype, value)
372        }
373    }
374
375    /// Compares numeric values of identical declared types.
376    ///
377    /// # Errors
378    /// Returns [`DataError::TypeMismatch`] for different declared types.
379    pub fn compare(self, rhs: Self) -> Result<Ordering, DataError> {
380        if self.dtype != rhs.dtype {
381            return Err(DataError::TypeMismatch);
382        }
383        let order = match self.dtype {
384            DType::F16 => Half::from_bits(self.bits).partial_cmp(&Half::from_bits(rhs.bits)),
385            DType::F32 => Single::from_bits(self.bits).partial_cmp(&Single::from_bits(rhs.bits)),
386            DType::F64 => Double::from_bits(self.bits).partial_cmp(&Double::from_bits(rhs.bits)),
387            DType::F128 => Quad::from_bits(self.bits).partial_cmp(&Quad::from_bits(rhs.bits)),
388            _ if self.dtype.is_signed() => Some(self.signed().cmp(&rhs.signed())),
389            _ => Some(self.bits.cmp(&rhs.bits)),
390        };
391        order.ok_or(DataError::NonFinite)
392    }
393}
394
395impl PartialEq for Scalar {
396    fn eq(&self, rhs: &Self) -> bool {
397        self.compare(*rhs) == Ok(Ordering::Equal)
398    }
399}
400impl Eq for Scalar {}
401
402impl core::ops::Neg for Scalar {
403    type Output = Result<Self, DataError>;
404
405    fn neg(self) -> Self::Output {
406        Scalar::neg(&self)
407    }
408}
409
410macro_rules! scalar_operator {
411    ($trait:ident, $method:ident, $operation:ident) => {
412        impl core::ops::$trait for Scalar {
413            type Output = Result<Self, DataError>;
414
415            fn $method(self, rhs: Self) -> Self::Output {
416                self.binary(BinaryOp::$operation, rhs)
417            }
418        }
419    };
420}
421
422scalar_operator!(Add, add, Add);
423scalar_operator!(Sub, sub, Sub);
424scalar_operator!(Mul, mul, Mul);
425scalar_operator!(Div, div, Div);
426scalar_operator!(Rem, rem, Rem);
427
428impl fmt::Display for Scalar {
429    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
430        match self.dtype {
431            DType::F16 => Half::from_bits(self.bits).fmt(f),
432            DType::F32 => Single::from_bits(self.bits).fmt(f),
433            DType::F64 => Double::from_bits(self.bits).fmt(f),
434            DType::F128 => Quad::from_bits(self.bits).fmt(f),
435            _ if self.dtype.is_signed() => self.signed().fmt(f),
436            _ => self.bits.fmt(f),
437        }
438    }
439}
440
441impl Serialize for Scalar {
442    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
443        super::wire::Scalar {
444            dtype: self.dtype,
445            value: self.to_string(),
446        }
447        .serialize(serializer)
448    }
449}
450
451impl<'de> Deserialize<'de> for Scalar {
452    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
453        let wire = super::wire::Scalar::deserialize(deserializer)?;
454        Self::parse(wire.dtype, &wire.value).map_err(serde::de::Error::custom)
455    }
456}
457
458fn mask(dtype: DType) -> u128 {
459    u128::MAX >> (128 - dtype.bit_width())
460}
461
462fn validate_decimal(text: &str) -> Result<(), DataError> {
463    let bytes = text.as_bytes();
464    let mut index = usize::from(matches!(bytes.first(), Some(b'+' | b'-')));
465    let mut digits = 0;
466    while bytes.get(index).is_some_and(u8::is_ascii_digit) {
467        index += 1;
468        digits += 1;
469    }
470    if bytes.get(index) == Some(&b'.') {
471        index += 1;
472        while bytes.get(index).is_some_and(u8::is_ascii_digit) {
473            index += 1;
474            digits += 1;
475        }
476    }
477    if digits == 0 {
478        return Err(DataError::InvalidLiteral);
479    }
480    if matches!(bytes.get(index), Some(b'e' | b'E')) {
481        index += 1;
482        if matches!(bytes.get(index), Some(b'+' | b'-')) {
483            index += 1;
484        }
485        let exponent_start = index;
486        while bytes.get(index).is_some_and(u8::is_ascii_digit) {
487            index += 1;
488        }
489        if exponent_start == index {
490            return Err(DataError::InvalidLiteral);
491        }
492    }
493    if index == bytes.len() {
494        Ok(())
495    } else {
496        Err(DataError::InvalidLiteral)
497    }
498}
499
500fn checked_float<F: Float>(result: StatusAnd<F>, exact: bool) -> Result<u128, DataError> {
501    if result.status.intersects(Status::OVERFLOW) {
502        return Err(DataError::Overflow);
503    }
504    if !result.value.is_finite() || result.status.intersects(Status::INVALID_OP) {
505        return Err(DataError::NonFinite);
506    }
507    if exact && result.status != Status::OK {
508        return Err(DataError::InexactConversion);
509    }
510    Ok(result.value.to_bits())
511}
512
513fn parse_float<F: Float>(text: &str) -> Result<u128, DataError> {
514    checked_float(
515        F::from_str_r(text, Round::NearestTiesToEven).map_err(|_| DataError::InvalidLiteral)?,
516        false,
517    )
518}
519
520fn integer_to_float<F: Float>(value: Scalar) -> Result<u128, DataError> {
521    let result = if value.dtype.is_signed() {
522        F::from_i128(value.signed())
523    } else {
524        F::from_u128(value.bits)
525    };
526    checked_float(result, true)
527}
528
529fn convert_float<F>(value: F, dtype: DType) -> Result<Scalar, DataError>
530where
531    F: Float
532        + FloatConvert<Half>
533        + FloatConvert<Single>
534        + FloatConvert<Double>
535        + FloatConvert<Quad>,
536{
537    if dtype.is_float() {
538        let mut loses_info = false;
539        let bits = match dtype {
540            DType::F16 => checked_float::<Half>(value.convert(&mut loses_info), true)?,
541            DType::F32 => checked_float::<Single>(value.convert(&mut loses_info), true)?,
542            DType::F64 => checked_float::<Double>(value.convert(&mut loses_info), true)?,
543            DType::F128 => checked_float::<Quad>(value.convert(&mut loses_info), true)?,
544            _ => return Err(DataError::TypeMismatch),
545        };
546        if loses_info {
547            return Err(DataError::InexactConversion);
548        }
549        return Scalar::from_bits(dtype, bits);
550    }
551    // APFloat's signed-integer conversion delegates through positive zero and
552    // can reset its exactness flag. Preserve this contract explicitly here.
553    if value.is_zero() && value.is_negative() {
554        return Err(DataError::InexactConversion);
555    }
556    let mut exact = true;
557    if dtype.is_signed() {
558        let result = value.to_i128_r(dtype.bit_width(), Round::TowardZero, &mut exact);
559        if result.status.intersects(Status::INVALID_OP) {
560            return Err(DataError::Overflow);
561        }
562        if !exact || result.status != Status::OK {
563            return Err(DataError::InexactConversion);
564        }
565        Scalar::from_signed(dtype, result.value)
566    } else {
567        let result = value.to_u128_r(dtype.bit_width(), Round::TowardZero, &mut exact);
568        if result.status.intersects(Status::INVALID_OP) {
569            return Err(DataError::Overflow);
570        }
571        if !exact || result.status != Status::OK {
572            return Err(DataError::InexactConversion);
573        }
574        Scalar::from_unsigned(dtype, result.value)
575    }
576}
577
578fn float_binary<F: Float>(left: u128, operation: BinaryOp, right: u128) -> Result<u128, DataError> {
579    let (left, right) = (F::from_bits(left), F::from_bits(right));
580    if matches!(operation, BinaryOp::Div | BinaryOp::Rem) && right.is_zero() {
581        return Err(DataError::DivisionByZero);
582    }
583    let result = match operation {
584        BinaryOp::Add => left + right,
585        BinaryOp::Sub => left - right,
586        BinaryOp::Mul => left * right,
587        BinaryOp::Div => left / right,
588        BinaryOp::Rem => left.c_fmod(right),
589    };
590    checked_float(result, false)
591}
592
593#[cfg(test)]
594mod tests {
595    use super::*;
596
597    #[test]
598    fn every_integer_width_checks_its_own_boundaries() {
599        for dtype in DType::ALL.into_iter().filter(|dtype| !dtype.is_float()) {
600            let zero = Scalar::zero(dtype);
601            let one = Scalar::parse(dtype, "1").unwrap();
602            assert_eq!(
603                one.binary(BinaryOp::Div, zero),
604                Err(DataError::DivisionByZero)
605            );
606            assert_eq!(
607                one.binary(BinaryOp::Rem, zero),
608                Err(DataError::DivisionByZero)
609            );
610            if dtype.is_signed() {
611                let shift = 128 - dtype.bit_width();
612                let minimum = Scalar::from_signed(dtype, i128::MIN >> shift).unwrap();
613                let maximum = Scalar::from_signed(dtype, i128::MAX >> shift).unwrap();
614                let negative_one = Scalar::parse(dtype, "-1").unwrap();
615                assert_eq!(minimum.neg(), Err(DataError::Overflow), "{dtype}");
616                assert_eq!(
617                    minimum.binary(BinaryOp::Sub, one),
618                    Err(DataError::Overflow),
619                    "{dtype}"
620                );
621                assert_eq!(
622                    maximum.binary(BinaryOp::Add, one),
623                    Err(DataError::Overflow),
624                    "{dtype}"
625                );
626                assert_eq!(
627                    minimum.binary(BinaryOp::Div, negative_one),
628                    Err(DataError::Overflow),
629                    "{dtype}"
630                );
631                assert_eq!(
632                    minimum.binary(BinaryOp::Rem, negative_one),
633                    Err(DataError::Overflow),
634                    "{dtype}"
635                );
636                assert_eq!(Scalar::parse(dtype, &minimum.to_string()).unwrap(), minimum);
637                assert_eq!(Scalar::parse(dtype, &maximum.to_string()).unwrap(), maximum);
638            } else {
639                let maximum = Scalar::from_unsigned(dtype, mask(dtype)).unwrap();
640                assert_eq!(
641                    maximum.binary(BinaryOp::Add, one),
642                    Err(DataError::Overflow),
643                    "{dtype}"
644                );
645                assert_eq!(
646                    zero.binary(BinaryOp::Sub, one),
647                    Err(DataError::Overflow),
648                    "{dtype}"
649                );
650                assert_eq!(one.neg(), Err(DataError::Overflow));
651                assert_eq!(zero.neg().unwrap(), zero);
652                assert_eq!(Scalar::parse(dtype, &maximum.to_string()).unwrap(), maximum);
653            }
654        }
655        assert!(Scalar::parse(DType::I128, "170141183460469231731687303715884105728").is_err());
656        assert!(Scalar::parse(DType::U128, "340282366920938463463374607431768211456").is_err());
657        assert!(Scalar::from_bits(DType::I8, u128::MAX).is_err());
658        assert_eq!(
659            Scalar::from_bits(DType::I8, 255).unwrap().as_i128(),
660            Some(-1)
661        );
662    }
663
664    #[test]
665    fn binary128_retains_values_beyond_binary64_precision_and_range() {
666        let one = Scalar::parse(DType::F128, "1").unwrap();
667        let next = Scalar::from_bits(DType::F128, one.bits() + 1).unwrap();
668        assert!(next.compare(one).unwrap().is_gt());
669        assert_eq!(next.convert(DType::F64), Err(DataError::InexactConversion));
670        assert_eq!(
671            Scalar::parse(DType::F128, &next.to_string())
672                .unwrap()
673                .bits(),
674            next.bits()
675        );
676        let large = Scalar::parse(DType::F128, "1e4000").unwrap();
677        assert_eq!(large.convert(DType::F64), Err(DataError::Overflow));
678        assert_eq!(large.binary(BinaryOp::Div, large).unwrap(), one);
679        assert_eq!(
680            Scalar::parse(DType::F128, "1e5000"),
681            Err(DataError::Overflow)
682        );
683    }
684
685    #[test]
686    fn decimal_parsing_rounds_once_at_the_declared_precision() {
687        // Just above the binary16 midpoint. Routing through f64 would erase the
688        // final decimal digits and incorrectly round the midpoint down to 1.
689        let value = Scalar::parse(DType::F16, "1.0004882812500000000000001").unwrap();
690        assert_eq!(value.bits(), 0x3c01);
691        assert_eq!(
692            Scalar::parse(DType::F16, "1.00048828125").unwrap().bits(),
693            0x3c00
694        );
695        assert_eq!(Scalar::parse(DType::F16, "65520"), Err(DataError::Overflow));
696        for text in [
697            "", " ", "NaN", "inf", "-inf", "1e", ".", "--1", "1_0", "0x1p0", "1\0",
698        ] {
699            assert!(Scalar::parse(DType::F128, text).is_err(), "{text:?}");
700        }
701        // Extremely long exponents cannot overflow the parser's host integer.
702        assert!(Scalar::parse(DType::F128, "1e999999999999999999999999999999999").is_err());
703    }
704
705    #[test]
706    fn conversions_reject_fraction_range_and_precision_loss() {
707        assert_eq!(
708            Scalar::parse(DType::U64, "9007199254740993")
709                .unwrap()
710                .convert(DType::F64),
711            Err(DataError::InexactConversion)
712        );
713        assert_eq!(
714            Scalar::parse(DType::U128, &u128::MAX.to_string())
715                .unwrap()
716                .convert(DType::F128),
717            Err(DataError::InexactConversion)
718        );
719        let minimum = Scalar::parse(DType::I128, &i128::MIN.to_string()).unwrap();
720        assert_eq!(
721            minimum
722                .convert(DType::F128)
723                .unwrap()
724                .convert(DType::I128)
725                .unwrap(),
726            minimum
727        );
728        assert_eq!(
729            Scalar::parse(DType::F64, "1.5")
730                .unwrap()
731                .convert(DType::I32),
732            Err(DataError::InexactConversion)
733        );
734        assert_eq!(
735            Scalar::parse(DType::I16, "128").unwrap().convert(DType::I8),
736            Err(DataError::Overflow)
737        );
738        assert_eq!(
739            Scalar::parse(DType::I8, "-1").unwrap().convert(DType::U128),
740            Err(DataError::Overflow)
741        );
742        assert_eq!(
743            Scalar::parse(DType::F32, "-0").unwrap().convert(DType::I32),
744            Err(DataError::InexactConversion)
745        );
746        assert_eq!(
747            Scalar::parse(DType::F32, "-0").unwrap().convert(DType::U32),
748            Err(DataError::InexactConversion)
749        );
750        let negative_zero = Scalar::parse(DType::F16, "-0").unwrap();
751        assert_eq!(negative_zero, Scalar::zero(DType::F16));
752        assert_eq!(negative_zero.neg().unwrap().bits(), 0);
753        assert_eq!(
754            negative_zero.convert(DType::F128).unwrap().bits(),
755            1_u128 << 127
756        );
757    }
758
759    #[test]
760    fn arithmetic_uses_declared_float_width_and_fmod_remainder() {
761        for dtype in [DType::F16, DType::F32, DType::F64, DType::F128] {
762            let seven = Scalar::parse(dtype, "-7.5").unwrap();
763            let two = Scalar::parse(dtype, "2").unwrap();
764            assert_eq!(
765                seven.binary(BinaryOp::Rem, two).unwrap(),
766                Scalar::parse(dtype, "-1.5").unwrap()
767            );
768            assert_eq!(
769                two.binary(BinaryOp::Div, Scalar::zero(dtype)),
770                Err(DataError::DivisionByZero)
771            );
772            assert_eq!(
773                two.binary(BinaryOp::Mul, two).unwrap(),
774                Scalar::parse(dtype, "4").unwrap()
775            );
776        }
777        assert!(Scalar::from_bits(DType::F16, 0x7c00).is_err());
778        assert!(Scalar::from_bits(DType::F32, 0x7fc00000).is_err());
779        assert!(Scalar::from_bits(DType::F64, 0x7ff0000000000000).is_err());
780        assert!(Scalar::from_bits(DType::F128, 0x7fff_u128 << 112).is_err());
781    }
782
783    #[test]
784    fn exact_wire_rejects_numeric_json_and_retains_all_wide_bits() {
785        for (dtype, text) in [
786            (DType::I128, "-170141183460469231731687303715884105728"),
787            (DType::U128, "340282366920938463463374607431768211455"),
788            (DType::F128, "1.0000000000000000000000000000000002"),
789            (DType::F128, "-0"),
790        ] {
791            let value = Scalar::parse(dtype, text).unwrap();
792            let wire = serde_json::to_string(&value).unwrap();
793            let result: Scalar = serde_json::from_str(&wire).unwrap();
794            assert_eq!(result.dtype(), dtype);
795            assert_eq!(result.bits(), value.bits());
796        }
797        for wire in [
798            r#"{"dtype":"u128","value":1}"#,
799            r#"{"dtype":"f128","value":"NaN"}"#,
800            r#"{"dtype":"i8","value":"128"}"#,
801        ] {
802            assert!(serde_json::from_str::<Scalar>(wire).is_err());
803        }
804    }
805
806    #[test]
807    fn every_finite_binary16_value_has_an_exact_decimal_roundtrip() {
808        for bits in 0..=u16::MAX {
809            if let Ok(value) = Scalar::from_bits(DType::F16, u128::from(bits)) {
810                let parsed = Scalar::parse(DType::F16, &value.to_string()).unwrap();
811                assert_eq!(value.bits(), parsed.bits(), "bits {bits:04x}");
812            }
813        }
814    }
815
816    #[test]
817    fn wide_float_text_roundtrips_cover_subnormal_and_extreme_exponents() {
818        for dtype in [DType::F32, DType::F64, DType::F128] {
819            let sign = 1_u128 << (dtype.bit_width() - 1);
820            let largest = match dtype {
821                DType::F32 => Single::largest().to_bits(),
822                DType::F64 => Double::largest().to_bits(),
823                DType::F128 => Quad::largest().to_bits(),
824                _ => unreachable!(),
825            };
826            for bits in [0, 1, 2, largest, largest - 1] {
827                for sign_bit in [0, sign] {
828                    let value = Scalar::from_bits(dtype, bits | sign_bit).unwrap();
829                    let parsed = Scalar::parse(dtype, &value.to_string()).unwrap();
830                    assert_eq!(value.bits(), parsed.bits(), "{dtype}: {value}");
831                }
832            }
833            let maximum = Scalar::from_bits(dtype, largest).unwrap();
834            let two = Scalar::parse(dtype, "2").unwrap();
835            assert_eq!(maximum.binary(BinaryOp::Mul, two), Err(DataError::Overflow));
836        }
837    }
838}