Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_datime/src/index/time_integer.rs

11.6 KiB, 49 runs

created by r1870400018:8468, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! Time represented as an abstract integer, so that arithmetic on it can be
2//! done without reference to a calendar.
3//!
4//! Two implementations are offered: TimeLong over an i64, and TimeBigInt over
5//! an arbitrary precision integer.
6//!
7//! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\
8//! Anthropic Claude
9
10use oxedyne_fe2o3_core::prelude::*;
11use num_bigint::BigInt;
12use num_traits::Zero;
13use std::str::FromStr;
14
15pub trait TimeInteger: Clone + PartialEq + Eq + PartialOrd + Ord + std::fmt::Debug {
16 fn is_zero(&self) -> bool;
17
18 fn is_positive(&self) -> bool;
19
20 fn negate(self) -> Self;
21
22 /// May truncate a value too large for an i64.
23 fn long_value(&self) -> i64;
24
25 fn num_bytes(&self) -> usize;
26
27 // Arithmetic operations
28 // The paired forms differ in how they fail. Those taking Self report
29 // overflow and division by zero as errors; those taking an i64 saturate,
30 // and give zero where the divisor is zero.
31 fn add_to(self, other: Self) -> Outcome<Self>;
32 fn add_to_long(self, other: i64) -> Self;
33 fn subtract_it(self, other: Self) -> Outcome<Self>;
34 fn subtract_it_long(self, other: i64) -> Self;
35 fn multiply_by(self, other: Self) -> Outcome<Self>;
36 fn multiply_by_long(self, other: i64) -> Self;
37 fn divide_by(self, other: Self) -> Outcome<Self>;
38 fn divide_by_long(self, other: i64) -> Self;
39 fn remainder_by(self, other: Self) -> Outcome<Self>;
40 fn remainder_by_long(self, other: i64) -> Self;
41
42 // Serialization
43 /// Big endian.
44 fn to_file_byte_array(&self) -> Vec<u8>;
45
46 /// Padded and truncated at the tail, not the head.
47 fn to_fixed_byte_array(&self, n: usize) -> Vec<u8>;
48
49 fn to_string_with_commas(&self) -> String;
50
51 fn from_bytes(bytes: &[u8]) -> Outcome<Self> where Self: Sized;
52
53 fn from_string(s: &str) -> Outcome<Self> where Self: Sized;
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
57pub struct TimeLong {
58 value: i64,
59}
60
61impl TimeLong {
62 pub fn new(value: i64) -> Self {
63 Self { value }
64 }
65
66 pub fn value(&self) -> i64 {
67 self.value
68 }
69
70 pub fn from_bytes_array(bytes: &[u8; 8]) -> Self {
71 let value = i64::from_be_bytes(*bytes);
72 Self::new(value)
73 }
74
75 pub fn to_bytes_array(&self) -> [u8; 8] {
76 self.value.to_be_bytes()
77 }
78}
79
80impl TimeInteger for TimeLong {
81 fn is_zero(&self) -> bool {
82 self.value == 0
83 }
84
85 fn is_positive(&self) -> bool {
86 self.value > 0
87 }
88
89 fn negate(self) -> Self {
90 Self::new(-self.value)
91 }
92
93 fn long_value(&self) -> i64 {
94 self.value
95 }
96
97 fn num_bytes(&self) -> usize {
98 8
99 }
100
101 fn add_to(self, other: Self) -> Outcome<Self> {
102 match self.value.checked_add(other.value) {
103 Some(result) => Ok(Self::new(result)),
104 None => Err(err!("Integer overflow in addition: {} + {}", self.value, other.value; Overflow)),
105 }
106 }
107
108 fn add_to_long(self, other: i64) -> Self {
109 Self::new(self.value.saturating_add(other))
110 }
111
112 fn subtract_it(self, other: Self) -> Outcome<Self> {
113 match self.value.checked_sub(other.value) {
114 Some(result) => Ok(Self::new(result)),
115 None => Err(err!("Integer overflow in subtraction: {} - {}", self.value, other.value; Overflow)),
116 }
117 }
118
119 fn subtract_it_long(self, other: i64) -> Self {
120 Self::new(self.value.saturating_sub(other))
121 }
122
123 fn multiply_by(self, other: Self) -> Outcome<Self> {
124 match self.value.checked_mul(other.value) {
125 Some(result) => Ok(Self::new(result)),
126 None => Err(err!("Integer overflow in multiplication: {} * {}", self.value, other.value; Overflow)),
127 }
128 }
129
130 fn multiply_by_long(self, other: i64) -> Self {
131 Self::new(self.value.saturating_mul(other))
132 }
133
134 fn divide_by(self, other: Self) -> Outcome<Self> {
135 if other.value == 0 {
136 return Err(err!("Division by zero"; Invalid, Input));
137 }
138 match self.value.checked_div(other.value) {
139 Some(result) => Ok(Self::new(result)),
140 None => Err(err!("Integer overflow in division: {} / {}", self.value, other.value; Overflow)),
141 }
142 }
143
144 fn divide_by_long(self, other: i64) -> Self {
145 if other == 0 {
146 return Self::new(0); // Saturating behaviour
147 }
148 Self::new(self.value / other)
149 }
150
151 fn remainder_by(self, other: Self) -> Outcome<Self> {
152 if other.value == 0 {
153 return Err(err!("Modulo by zero"; Invalid, Input));
154 }
155 Ok(Self::new(self.value % other.value))
156 }
157
158 fn remainder_by_long(self, other: i64) -> Self {
159 if other == 0 {
160 return Self::new(0); // Saturating behaviour
161 }
162 Self::new(self.value % other)
163 }
164
165 fn to_file_byte_array(&self) -> Vec<u8> {
166 self.value.to_be_bytes().to_vec()
167 }
168
169 fn to_fixed_byte_array(&self, n: usize) -> Vec<u8> {
170 let mut bytes = self.to_file_byte_array();
171 bytes.resize(n, 0);
172 bytes
173 }
174
175 fn to_string_with_commas(&self) -> String {
176 let s = self.value.to_string();
177 add_commas_to_number(&s)
178 }
179
180 fn from_bytes(bytes: &[u8]) -> Outcome<Self> {
181 if bytes.len() != 8 {
182 return Err(err!("TimeLong requires exactly 8 bytes, got {}", bytes.len(); Invalid, Input));
183 }
184 let mut array = [0u8; 8];
185 array.copy_from_slice(bytes);
186 Ok(Self::from_bytes_array(&array))
187 }
188
189 fn from_string(s: &str) -> Outcome<Self> {
190 let cleaned = s.replace(",", "");
191 match cleaned.parse::<i64>() {
192 Ok(value) => Ok(Self::new(value)),
193 Err(e) => Err(err!("Failed to parse TimeLong from string '{}': {}", s, e; Invalid, Input)),
194 }
195 }
196}
197
198impl std::fmt::Display for TimeLong {
199 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200 write!(f, "{}", self.value)
201 }
202}
203
204impl From<i64> for TimeLong {
205 fn from(value: i64) -> Self {
206 Self::new(value)
207 }
208}
209
210impl From<TimeLong> for i64 {
211 fn from(time_long: TimeLong) -> Self {
212 time_long.value
213 }
214}
215
216#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
217pub struct TimeBigInt {
218 value: BigInt,
219}
220
221impl TimeBigInt {
222 pub fn new(value: BigInt) -> Self {
223 Self { value }
224 }
225
226 pub fn from_i64(value: i64) -> Self {
227 Self::new(BigInt::from(value))
228 }
229
230 pub fn value(&self) -> &BigInt {
231 &self.value
232 }
233
234 pub fn max_positive(n_bytes: usize) -> Outcome<Self> {
235 if n_bytes == 0 {
236 return Err(err!("Cannot create max positive value with 0 bytes"; Invalid, Input));
237 }
238
239 // Create 2^(8*n_bytes - 1) - 1 (maximum signed value)
240 let mut bytes = vec![0x7f]; // First byte: 0111 1111
241 bytes.extend(vec![0xff; n_bytes - 1]); // Remaining bytes: 1111 1111
242
243 Ok(Self::new(BigInt::from_bytes_be(num_bigint::Sign::Plus, &bytes)))
244 }
245}
246
247impl TimeInteger for TimeBigInt {
248 fn is_zero(&self) -> bool {
249 self.value.is_zero()
250 }
251
252 fn is_positive(&self) -> bool {
253 self.value > BigInt::from(0)
254 }
255
256 fn negate(self) -> Self {
257 Self::new(-self.value)
258 }
259
260 fn long_value(&self) -> i64 {
261 // Convert to i64, clamping to avoid overflow
262 use num_traits::ToPrimitive;
263 self.value.to_i64().unwrap_or_else(|| {
264 if self.value > BigInt::from(0) {
265 i64::MAX
266 } else {
267 i64::MIN
268 }
269 })
270 }
271
272 fn num_bytes(&self) -> usize {
273 let (_, bytes) = self.value.to_bytes_be();
274 bytes.len()
275 }
276
277 fn add_to(self, other: Self) -> Outcome<Self> {
278 Ok(Self::new(&self.value + &other.value))
279 }
280
281 fn add_to_long(self, other: i64) -> Self {
282 Self::new(&self.value + BigInt::from(other))
283 }
284
285 fn subtract_it(self, other: Self) -> Outcome<Self> {
286 Ok(Self::new(&self.value - &other.value))
287 }
288
289 fn subtract_it_long(self, other: i64) -> Self {
290 Self::new(&self.value - BigInt::from(other))
291 }
292
293 fn multiply_by(self, other: Self) -> Outcome<Self> {
294 Ok(Self::new(&self.value * &other.value))
295 }
296
297 fn multiply_by_long(self, other: i64) -> Self {
298 Self::new(&self.value * BigInt::from(other))
299 }
300
301 fn divide_by(self, other: Self) -> Outcome<Self> {
302 if other.value.is_zero() {
303 return Err(err!("Division by zero"; Invalid, Input));
304 }
305 Ok(Self::new(&self.value / &other.value))
306 }
307
308 fn divide_by_long(self, other: i64) -> Self {
309 if other == 0 {
310 return Self::new(BigInt::from(0)); // Saturating behaviour
311 }
312 Self::new(&self.value / BigInt::from(other))
313 }
314
315 fn remainder_by(self, other: Self) -> Outcome<Self> {
316 if other.value.is_zero() {
317 return Err(err!("Modulo by zero"; Invalid, Input));
318 }
319 Ok(Self::new(&self.value % &other.value))
320 }
321
322 fn remainder_by_long(self, other: i64) -> Self {
323 if other == 0 {
324 return Self::new(BigInt::from(0)); // Saturating behaviour
325 }
326 Self::new(&self.value % BigInt::from(other))
327 }
328
329 fn to_file_byte_array(&self) -> Vec<u8> {
330 let (_, bytes) = self.value.to_bytes_be();
331 bytes
332 }
333
334 fn to_fixed_byte_array(&self, n: usize) -> Vec<u8> {
335 let bytes = self.to_file_byte_array();
336 if bytes.len() < n {
337 // Pad with zeros at the beginning for big endian
338 let mut padded = vec![0; n - bytes.len()];
339 padded.extend(bytes);
340 padded
341 } else if bytes.len() > n {
342 // Truncate from the beginning
343 bytes[bytes.len() - n..].to_vec()
344 } else {
345 bytes
346 }
347 }
348
349 fn to_string_with_commas(&self) -> String {
350 let s = self.value.to_string();
351 add_commas_to_number(&s)
352 }
353
354 fn from_bytes(bytes: &[u8]) -> Outcome<Self> {
355 if bytes.is_empty() {
356 return Ok(Self::new(BigInt::from(0)));
357 }
358
359 // Determine sign from the first bit
360 let sign = if bytes[0] & 0x80 == 0 {
361 num_bigint::Sign::Plus
362 } else {
363 num_bigint::Sign::Minus
364 };
365
366 Ok(Self::new(BigInt::from_bytes_be(sign, bytes)))
367 }
368
369 fn from_string(s: &str) -> Outcome<Self> {
370 let cleaned = s.replace(",", "");
371 match BigInt::from_str(&cleaned) {
372 Ok(value) => Ok(Self::new(value)),
373 Err(e) => Err(err!("Failed to parse TimeBigInt from string '{}': {}", s, e; Invalid, Input)),
374 }
375 }
376}
377
378impl std::fmt::Display for TimeBigInt {
379 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
380 write!(f, "{}", self.value)
381 }
382}
383
384impl From<i64> for TimeBigInt {
385 fn from(value: i64) -> Self {
386 Self::from_i64(value)
387 }
388}
389
390impl From<BigInt> for TimeBigInt {
391 fn from(value: BigInt) -> Self {
392 Self::new(value)
393 }
394}
395
396fn add_commas_to_number(s: &str) -> String {
397 if s.is_empty() {
398 return s.to_string();
399 }
400
401 let (sign, digits) = if s.starts_with('-') {
402 ("-", &s[1..])
403 } else {
404 ("", s)
405 };
406
407 let mut result = String::new();
408 result.push_str(sign);
409
410 let chars: Vec<char> = digits.chars().collect();
411 let len = chars.len();
412
413 for (i, &ch) in chars.iter().enumerate() {
414 result.push(ch);
415 let remaining = len - i - 1;
416 if remaining > 0 && remaining % 3 == 0 {
417 result.push(',');
418 }
419 }
420
421 result
422}
423
424#[cfg(test)]
425mod tests {
426 use super::*;
427
428 #[test]
429 fn test_time_long_basic_operations() {
430 let a = TimeLong::new(100);
431 let b = TimeLong::new(50);
432
433 assert_eq!(a.clone().add_to(b.clone()).unwrap().value(), 150);
434 assert_eq!(a.clone().subtract_it(b.clone()).unwrap().value(), 50);
435 assert_eq!(a.clone().multiply_by(b.clone()).unwrap().value(), 5000);
436 assert_eq!(a.clone().divide_by(b.clone()).unwrap().value(), 2);
437 assert_eq!(a.remainder_by(b).unwrap().value(), 0);
438 }
439
440 #[test]
441 fn test_time_long_serialization() {
442 let original = TimeLong::new(123456789);
443 let bytes = original.to_file_byte_array();
444 let restored = TimeLong::from_bytes(&bytes).unwrap();
445 assert_eq!(original, restored);
446 }
447
448 #[test]
449 fn test_time_long_commas() {
450 let num = TimeLong::new(1234567890);
451 assert_eq!(num.to_string_with_commas(), "1,234,567,890");
452 }
453
454 #[test]
455 fn test_time_big_int_basic_operations() {
456 let a = TimeBigInt::from_i64(100);
457 let b = TimeBigInt::from_i64(50);
458
459 assert_eq!(a.clone().add_to(b.clone()).unwrap().long_value(), 150);
460 assert_eq!(a.clone().subtract_it(b.clone()).unwrap().long_value(), 50);
461 assert_eq!(a.clone().multiply_by(b.clone()).unwrap().long_value(), 5000);
462 assert_eq!(a.clone().divide_by(b.clone()).unwrap().long_value(), 2);
463 assert_eq!(a.remainder_by(b).unwrap().long_value(), 0);
464 }
465
466 #[test]
467 fn test_add_commas_to_number() {
468 assert_eq!(add_commas_to_number("1234567890"), "1,234,567,890");
469 assert_eq!(add_commas_to_number("-1234567890"), "-1,234,567,890");
470 assert_eq!(add_commas_to_number("123"), "123");
471 assert_eq!(add_commas_to_number(""), "");
472 }
473}