1use simdnbt::{
4 borrow::{NbtCompound as BorrowedNbtCompound, NbtTag as BorrowedNbtTag},
5 owned::{NbtCompound as OwnedNbtCompound, NbtTag as OwnedNbtTag},
6};
7
8#[derive(Clone, Copy, Debug, PartialEq)]
14#[expect(missing_docs, reason = "each variant is the NBT tag of that name")]
15pub enum NumericTag {
16 Byte(i8),
17 Short(i16),
18 Int(i32),
19 Long(i64),
20 Float(f32),
21 Double(f64),
22}
23
24impl NumericTag {
25 const fn floor(value: f64) -> i32 {
26 value.floor() as i32
27 }
28
29 #[must_use]
31 pub fn byte_value(self) -> i8 {
32 match self {
33 Self::Byte(value) => value,
34 Self::Short(value) => value as i8,
35 Self::Int(value) => value as i8,
36 Self::Long(value) => value as i8,
37 Self::Float(value) => Self::floor(f64::from(value)) as i8,
38 Self::Double(value) => Self::floor(value) as i8,
39 }
40 }
41
42 #[must_use]
44 pub fn short_value(self) -> i16 {
45 match self {
46 Self::Byte(value) => i16::from(value),
47 Self::Short(value) => value,
48 Self::Int(value) => value as i16,
49 Self::Long(value) => value as i16,
50 Self::Float(value) => Self::floor(f64::from(value)) as i16,
51 Self::Double(value) => Self::floor(value) as i16,
52 }
53 }
54
55 #[must_use]
57 pub fn int_value(self) -> i32 {
58 match self {
59 Self::Byte(value) => i32::from(value),
60 Self::Short(value) => i32::from(value),
61 Self::Int(value) => value,
62 Self::Long(value) => value as i32,
63 Self::Float(value) => Self::floor(f64::from(value)),
64 Self::Double(value) => Self::floor(value),
65 }
66 }
67
68 #[must_use]
70 pub fn long_value(self) -> i64 {
71 match self {
72 Self::Byte(value) => i64::from(value),
73 Self::Short(value) => i64::from(value),
74 Self::Int(value) => i64::from(value),
75 Self::Long(value) => value,
76 Self::Float(value) => value as i64,
77 Self::Double(value) => value.floor() as i64,
78 }
79 }
80
81 #[must_use]
83 pub fn float_value(self) -> f32 {
84 match self {
85 Self::Byte(value) => f32::from(value),
86 Self::Short(value) => f32::from(value),
87 Self::Int(value) => value as f32,
88 Self::Long(value) => value as f32,
89 Self::Float(value) => value,
90 Self::Double(value) => value as f32,
91 }
92 }
93
94 #[must_use]
96 pub fn double_value(self) -> f64 {
97 match self {
98 Self::Byte(value) => f64::from(value),
99 Self::Short(value) => f64::from(value),
100 Self::Int(value) => f64::from(value),
101 Self::Long(value) => value as f64,
102 Self::Float(value) => f64::from(value),
103 Self::Double(value) => value,
104 }
105 }
106}
107
108impl TryFrom<&OwnedNbtTag> for NumericTag {
109 type Error = ();
110
111 fn try_from(tag: &OwnedNbtTag) -> Result<Self, Self::Error> {
112 match tag {
113 OwnedNbtTag::Byte(value) => Ok(Self::Byte(*value)),
114 OwnedNbtTag::Short(value) => Ok(Self::Short(*value)),
115 OwnedNbtTag::Int(value) => Ok(Self::Int(*value)),
116 OwnedNbtTag::Long(value) => Ok(Self::Long(*value)),
117 OwnedNbtTag::Float(value) => Ok(Self::Float(*value)),
118 OwnedNbtTag::Double(value) => Ok(Self::Double(*value)),
119 _ => Err(()),
120 }
121 }
122}
123
124impl TryFrom<&BorrowedNbtTag<'_, '_>> for NumericTag {
125 type Error = ();
126
127 fn try_from(tag: &BorrowedNbtTag<'_, '_>) -> Result<Self, Self::Error> {
128 tag.byte()
129 .map(Self::Byte)
130 .or_else(|| tag.short().map(Self::Short))
131 .or_else(|| tag.int().map(Self::Int))
132 .or_else(|| tag.long().map(Self::Long))
133 .or_else(|| tag.float().map(Self::Float))
134 .or_else(|| tag.double().map(Self::Double))
135 .ok_or(())
136 }
137}
138
139pub trait NbtValueInput {
145 fn numeric_tag(&self, name: &str) -> Option<NumericTag>;
147
148 fn byte_value(&self, name: &str) -> Option<i8> {
150 self.numeric_tag(name).map(NumericTag::byte_value)
151 }
152
153 fn short_value(&self, name: &str) -> Option<i16> {
155 self.numeric_tag(name).map(NumericTag::short_value)
156 }
157
158 fn int_value(&self, name: &str) -> Option<i32> {
160 self.numeric_tag(name).map(NumericTag::int_value)
161 }
162
163 fn long_value(&self, name: &str) -> Option<i64> {
165 self.numeric_tag(name).map(NumericTag::long_value)
166 }
167
168 fn float_value(&self, name: &str) -> Option<f32> {
170 self.numeric_tag(name).map(NumericTag::float_value)
171 }
172
173 fn double_value(&self, name: &str) -> Option<f64> {
175 self.numeric_tag(name).map(NumericTag::double_value)
176 }
177}
178
179impl NbtValueInput for OwnedNbtCompound {
180 fn numeric_tag(&self, name: &str) -> Option<NumericTag> {
181 self.get(name)?.try_into().ok()
182 }
183}
184
185impl NbtValueInput for BorrowedNbtCompound<'_, '_> {
186 fn numeric_tag(&self, name: &str) -> Option<NumericTag> {
187 (&self.get(name)?).try_into().ok()
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use std::io::Cursor;
194
195 use simdnbt::borrow::read_tag;
196 use simdnbt::owned::{NbtCompound, NbtTag};
197
198 use super::{NbtValueInput as _, NumericTag};
199
200 #[test]
201 fn integer_tags_narrow_by_keeping_low_bits() {
202 assert_eq!(NumericTag::Short(0x1FF).byte_value(), -1);
203 assert_eq!(NumericTag::Int(0x1_FFFF).short_value(), -1);
204 assert_eq!(
205 NumericTag::Long(i64::from(i32::MAX) + 1).int_value(),
206 i32::MIN
207 );
208 assert_eq!(NumericTag::Long(0x1FF).byte_value(), -1);
209 }
210
211 #[test]
212 fn floating_tags_floor_into_integers() {
213 assert_eq!(NumericTag::Float(-1.5).int_value(), -2);
214 assert_eq!(NumericTag::Double(-1.5).int_value(), -2);
215 assert_eq!(NumericTag::Float(5.9).int_value(), 5);
216 assert_eq!(NumericTag::Double(-0.5).short_value(), -1);
217 assert_eq!(NumericTag::Float(-0.5).byte_value(), -1);
218 }
219
220 #[test]
221 fn float_long_value_truncates_but_double_floors() {
222 assert_eq!(NumericTag::Float(-1.5).long_value(), -1);
223 assert_eq!(NumericTag::Double(-1.5).long_value(), -2);
224 }
225
226 #[test]
227 fn floating_tags_saturate_before_narrowing() {
228 assert_eq!(NumericTag::Double(1e300).int_value(), i32::MAX);
229 assert_eq!(NumericTag::Double(1e300).byte_value(), -1);
230 assert_eq!(NumericTag::Float(f32::NAN).int_value(), 0);
231 }
232
233 #[test]
234 fn compound_reads_accept_any_numeric_tag() {
235 let mut nbt = NbtCompound::new();
236 nbt.insert("Radius", 1);
237 nbt.insert("Duration", 200i16);
238 nbt.insert("Name", "1");
239
240 assert_eq!(nbt.float_value("Radius"), Some(1.0));
241 assert_eq!(nbt.int_value("Duration"), Some(200));
242 assert_eq!(nbt.int_value("Name"), None);
243 assert_eq!(nbt.int_value("Missing"), None);
244 }
245
246 #[test]
247 fn borrowed_compound_reads_accept_any_numeric_tag() {
248 let mut nbt = NbtCompound::new();
249 nbt.insert("Radius", 1);
250 let mut bytes = Vec::new();
251 NbtTag::Compound(nbt).write(&mut bytes);
252 let borrowed =
253 read_tag(&mut Cursor::new(bytes.as_slice())).expect("owned test tag should parse");
254 let tag = borrowed.as_tag();
255 let compound = tag.compound().expect("test tag is a compound");
256
257 assert_eq!(compound.float_value("Radius"), Some(1.0));
258 }
259}