Skip to content

Commit 673ee44

Browse files
committed
chore: merge double-int into repo
1 parent 1525e1e commit 673ee44

File tree

5 files changed

+348
-0
lines changed

5 files changed

+348
-0
lines changed

Cargo.lock

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/double-int/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Changelog
2+
3+
## Unreleased
4+
5+
## 0.1.0
6+
7+
- Initial release with `DoubleInt` type.

crates/double-int/Cargo.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[package]
2+
name = "double-int"
3+
version = "0.1.0"
4+
description = "The double-int format represents an integer that can be stored in an IEEE 754 double-precision number without loss of precision"
5+
categories = ["encoding", "no-std", "no-std::no-alloc"]
6+
keywords = ["serde", "utilities", "serialization", "deserialization"]
7+
authors.workspace = true
8+
repository.workspace = true
9+
license.workspace = true
10+
edition.workspace = true
11+
rust-version.workspace = true
12+
13+
[dependencies]
14+
serde = { version = "1", default-features = false }
15+
16+
[dev-dependencies]
17+
serde = { version = "1", features = ["derive"] }
18+
serde_json = "1"
19+
toml = "0.8"

crates/double-int/README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# `double-int`
2+
3+
<!-- prettier-ignore-start -->
4+
5+
[![crates.io](https://img.shields.io/crates/v/double-int?label=latest)](https://crates.io/crates/double-int)
6+
[![Documentation](https://docs.rs/double-int/badge.svg?version=0.1.3)](https://docs.rs/double-int/0.1.3)
7+
[![dependency status](https://deps.rs/crate/double-int/0.1.3/status.svg)](https://deps.rs/crate/double-int/0.1.3)
8+
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/double-int.svg)
9+
<br />
10+
[![CI](https://github.com/x52dev/double-int/actions/workflows/ci.yml/badge.svg)](https://github.com/x52dev/double-int/actions/workflows/ci.yml)
11+
[![codecov](https://codecov.io/gh/x52dev/double-int/branch/main/graph/badge.svg)](https://codecov.io/gh/x52dev/double-int)
12+
![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg)
13+
[![Download](https://img.shields.io/crates/d/double-int.svg)](https://crates.io/crates/double-int)
14+
15+
<!-- prettier-ignore-end -->
16+
17+
<!-- cargo-rdme start -->
18+
19+
The double-int format represents an integer that can be stored in an IEEE 754 double-precision number without loss of precision.
20+
21+
This crate has been designed for use with OpenAPI tooling that wish to support integer-based `format: double-int` fields. [See docs in the OpenAPI format registry.][reg_double_int]
22+
23+
## Examples
24+
25+
```rust
26+
#[derive(Debug, serde::Deserialize)]
27+
struct Config {
28+
count: DoubleInt,
29+
}
30+
31+
let config = toml::from_str::<Config>(r#"
32+
count = 42
33+
"#).unwrap();
34+
assert_eq!(config.count, 42);
35+
36+
let config = toml::from_str::<Config>(r#"
37+
count = -42
38+
"#).unwrap();
39+
assert_eq!(config.count, -42);
40+
41+
// count is outside the bounds of a double-int (> 2^53 in this case)
42+
// (this would usually be accepted by an i64)
43+
let config = toml::from_str::<Config>(r#"
44+
count = 36028797018963968
45+
"#).unwrap_err();
46+
```
47+
48+
[reg_double_int]: https://spec.openapis.org/registry/format/double-int
49+
50+
<!-- cargo-rdme end -->

crates/double-int/src/lib.rs

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
//! The double-int format represents an integer that can be stored in an IEEE 754 double-precision
2+
//! number without loss of precision.
3+
//!
4+
//! This crate has been designed for use with OpenAPI tooling that wish to support integer-based
5+
//! `format: double-int` fields. [See docs in the OpenAPI format registry.][reg_double_int]
6+
//!
7+
//! # Examples
8+
//!
9+
//! ```
10+
//! # use double_int::DoubleInt;
11+
//! #[derive(Debug, serde::Deserialize)]
12+
//! struct Config {
13+
//! count: DoubleInt,
14+
//! }
15+
//!
16+
//! let config = toml::from_str::<Config>(r#"
17+
//! count = 42
18+
//! "#).unwrap();
19+
//! assert_eq!(config.count, 42);
20+
//!
21+
//! let config = toml::from_str::<Config>(r#"
22+
//! count = -42
23+
//! "#).unwrap();
24+
//! assert_eq!(config.count, -42);
25+
//!
26+
//! // count is outside the bounds of a double-int (> 2^53 in this case)
27+
//! // (this would usually be accepted by an i64)
28+
//! let config = toml::from_str::<Config>(r#"
29+
//! count = 36028797018963968
30+
//! "#).unwrap_err();
31+
//! ```
32+
//!
33+
//! [reg_double_int]: https://spec.openapis.org/registry/format/double-int
34+
35+
#![no_std]
36+
#![deny(rust_2018_idioms, nonstandard_style, future_incompatible)]
37+
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
38+
39+
use serde::{Deserialize, Deserializer, Serialize, Serializer};
40+
41+
/// Type that only deserializes from the `true` boolean value.
42+
///
43+
/// # Examples
44+
///
45+
/// ```
46+
/// assert_eq!(
47+
/// serde_json::from_str::<double_int::DoubleInt>("42").unwrap(),
48+
/// 42,
49+
/// );
50+
///
51+
/// serde_json::from_str::<double_int::DoubleInt>("4.2").unwrap_err();
52+
/// serde_json::from_str::<double_int::DoubleInt>("36028797018963968").unwrap_err();
53+
/// ```
54+
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
55+
pub struct DoubleInt(i64);
56+
57+
impl DoubleInt {
58+
const MIN: i128 = -(2_i128.pow(53)) + 1;
59+
const MAX: i128 = 2_i128.pow(53) - 1;
60+
const UMAX: u128 = 2_u128.pow(53) - 1;
61+
62+
/// Returns value as a standard type.
63+
pub const fn as_i64(self) -> i64 {
64+
self.0
65+
}
66+
}
67+
68+
macro_rules! from_impl {
69+
($ty:ty) => {
70+
impl From<$ty> for DoubleInt {
71+
fn from(val: $ty) -> Self {
72+
Self(val as i64)
73+
}
74+
}
75+
};
76+
}
77+
78+
from_impl!(u8);
79+
from_impl!(u16);
80+
from_impl!(u32);
81+
from_impl!(i8);
82+
from_impl!(i16);
83+
from_impl!(i32);
84+
85+
macro_rules! infallible_eq_impls {
86+
($ty:ty) => {
87+
impl PartialEq<$ty> for DoubleInt {
88+
fn eq(&self, val: &$ty) -> bool {
89+
self.0 == *val as i64
90+
}
91+
}
92+
};
93+
}
94+
95+
infallible_eq_impls!(u8);
96+
infallible_eq_impls!(u16);
97+
infallible_eq_impls!(u32);
98+
infallible_eq_impls!(i8);
99+
infallible_eq_impls!(i16);
100+
infallible_eq_impls!(i32);
101+
102+
impl PartialEq<u64> for DoubleInt {
103+
fn eq(&self, val: &u64) -> bool {
104+
match *val as u128 {
105+
// self cannot be larger than UMAX so val is not equal
106+
DoubleInt::UMAX.. => false,
107+
108+
// all remaining u64s would be representable by i64
109+
// just cast and check equality
110+
_ => self.0 == *val as i64,
111+
}
112+
}
113+
}
114+
115+
impl PartialEq<u128> for DoubleInt {
116+
fn eq(&self, val: &u128) -> bool {
117+
match val {
118+
// self cannot be larger than UMAX so val is not equal
119+
DoubleInt::UMAX.. => false,
120+
121+
// all remaining u64s would be representable by i64
122+
// just cast and check equality
123+
_ => self.0 == *val as i64,
124+
}
125+
}
126+
}
127+
128+
impl PartialEq<i64> for DoubleInt {
129+
fn eq(&self, val: &i64) -> bool {
130+
self.0 == *val
131+
}
132+
}
133+
134+
impl PartialEq<i128> for DoubleInt {
135+
fn eq(&self, val: &i128) -> bool {
136+
match val {
137+
// self cannot be larger than UMAX so val is not equal
138+
DoubleInt::MAX.. => false,
139+
140+
// all remaining u64s would be representable by i64
141+
// just cast and check equality
142+
_ => self.0 == *val as i64,
143+
}
144+
}
145+
}
146+
147+
impl<'de> Deserialize<'de> for DoubleInt {
148+
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
149+
match i64::deserialize(deserializer) {
150+
Err(err) => Err(err),
151+
152+
Ok(val) if (val as i128) < DoubleInt::MIN => Err(serde::de::Error::invalid_value(
153+
serde::de::Unexpected::Signed(val),
154+
&"integer larger than -9007199254740991 / -(2^53) + 1",
155+
)),
156+
157+
Ok(val) if (val as i128) > DoubleInt::MAX => Err(serde::de::Error::invalid_value(
158+
serde::de::Unexpected::Signed(val),
159+
&"integer smaller than 9007199254740991 / (2^53) - 1",
160+
)),
161+
162+
Ok(val) => Ok(DoubleInt(val)),
163+
}
164+
}
165+
}
166+
167+
impl Serialize for DoubleInt {
168+
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
169+
serializer.serialize_i64(self.0)
170+
}
171+
}
172+
173+
// #[cfg(test)]
174+
// mod tests {
175+
// use super::*;
176+
177+
// #[derive(Debug, Deserialize, PartialEq)]
178+
// struct Tru {
179+
// foo: DoubleInt,
180+
// }
181+
182+
// #[test]
183+
// fn de_true() {
184+
// assert_eq!(
185+
// Tru { foo: DoubleInt },
186+
// serde_json::from_str::<Tru>(r#"{"foo": true}"#).unwrap(),
187+
// );
188+
189+
// serde_json::from_str::<Tru>(r#"{"foo": false}"#).unwrap_err();
190+
// serde_json::from_str::<Tru>(r#"{"foo": 42}"#).unwrap_err();
191+
// }
192+
193+
// #[derive(Debug, Deserialize, PartialEq)]
194+
// struct Fal {
195+
// foo: False,
196+
// }
197+
198+
// #[test]
199+
// fn de_false() {
200+
// assert_eq!(
201+
// Fal { foo: False },
202+
// serde_json::from_str::<Fal>(r#"{"foo": false}"#).unwrap(),
203+
// );
204+
205+
// serde_json::from_str::<Fal>(r#"{"foo": true}"#).unwrap_err();
206+
// serde_json::from_str::<Fal>(r#"{"foo": 42}"#).unwrap_err();
207+
// }
208+
209+
// #[test]
210+
// fn ser() {
211+
// assert_eq!("true", serde_json::to_string(&DoubleInt).unwrap());
212+
// assert_eq!("false", serde_json::to_string(&False).unwrap());
213+
// }
214+
215+
// #[test]
216+
// fn as_bool() {
217+
// assert!(DoubleInt.as_bool());
218+
// assert!(!False.as_bool());
219+
// }
220+
221+
// #[test]
222+
// fn from() {
223+
// assert!(bool::from(DoubleInt));
224+
// assert!(!bool::from(False));
225+
// }
226+
227+
// #[test]
228+
// fn eq() {
229+
// assert_eq!(DoubleInt, DoubleInt);
230+
// assert_eq!(DoubleInt, true);
231+
// assert_eq!(true, DoubleInt);
232+
// assert_eq!(False, False);
233+
// assert_eq!(False, false);
234+
// assert_eq!(false, False);
235+
236+
// assert_ne!(DoubleInt, False);
237+
// assert_ne!(DoubleInt, false);
238+
// assert_ne!(False, DoubleInt);
239+
// assert_ne!(false, DoubleInt);
240+
241+
// assert_ne!(False, DoubleInt);
242+
// assert_ne!(False, true);
243+
// assert_ne!(DoubleInt, False);
244+
// assert_ne!(true, False);
245+
// }
246+
247+
// #[test]
248+
// fn formatting() {
249+
// let _ = format_args!("{:?}", DoubleInt);
250+
// let _ = format_args!("{:?}", False);
251+
// }
252+
253+
// #[test]
254+
// fn other_implementations() {
255+
// #![allow(clippy::default_constructed_unit_structs)]
256+
257+
// assert_eq!(DoubleInt.clone(), DoubleInt);
258+
// assert_eq!(False.clone(), False);
259+
260+
// assert_eq!(DoubleInt::default(), DoubleInt);
261+
// assert_eq!(False::default(), False);
262+
// }
263+
// }

0 commit comments

Comments
 (0)