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 #[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 pub const ALL: [Self; 14] = [$(Self::$variant,)+];
24
25 pub const fn name(self) -> &'static str {
27 match self { $(Self::$variant => $name,)+ }
28 }
29
30 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 pub const fn bit_width(self) -> usize {
37 match self { $(Self::$variant => $bits,)+ }
38 }
39
40 pub const fn byte_width(self) -> usize { self.bit_width() / 8 }
42
43 pub const fn bytes(self) -> usize { self.byte_width() }
45
46 pub const fn is_signed(self) -> bool {
48 match self { $(Self::$variant => $signed,)+ }
49 }
50
51 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
94pub enum BinaryOp {
95 Add,
97 Sub,
99 Mul,
101 Div,
103 Rem,
105}
106
107#[derive(Clone, Copy, Debug)]
114pub struct Scalar {
115 dtype: DType,
116 bits: u128,
117}
118
119impl Scalar {
120 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 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 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 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 pub const fn zero(dtype: DType) -> Self {
216 Self { dtype, bits: 0 }
217 }
218
219 pub const fn dtype(self) -> DType {
221 self.dtype
222 }
223
224 pub const fn bits(self) -> u128 {
226 self.bits
227 }
228
229 pub fn as_i128(self) -> Option<i128> {
231 self.dtype.is_signed().then(|| self.signed())
232 }
233
234 pub fn as_u128(self) -> Option<u128> {
236 (!self.dtype.is_signed() && !self.dtype.is_float()).then_some(self.bits)
237 }
238
239 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 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 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 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 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 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 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 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 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}