1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
//! A positive `multipleOf` divisor over the `number` domain.
use std::cmp::Ordering;
use fraction::{BigFraction, Integer};
use jsonschema_value::numeric_check::{divisor_kind, satisfies_multiple_of, DivisorKind};
use num_traits::{One, Zero};
use serde_json::Number;
use super::{normalized_number, BoundInteger};
/// A divisor kept in the spelling the validator will read, so membership matches it exactly. The
/// exact rational alongside it is what the spelling denotes, and is absent when no rational this
/// build can hold spells back to it - membership stands either way, only arithmetic needs it.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct BoundRational {
limit: Number,
value: Option<BigFraction>,
}
/// What a divisor becomes over the integers it admits.
enum IntegerFold {
/// Every integer is a multiple, so the divisor leaves no trace.
Vacuous,
Divisor(BoundInteger),
/// The integer form would take different arithmetic than the divisor as written.
Unfaithful,
}
impl BoundRational {
/// `None` only when the divisor has no `f64` at all, which is where the validator drops the
/// keyword. The rational is kept when it spells back to the divisor, which is what lets the
/// exact arithmetic below stand in for the spelling.
pub(crate) fn new(limit: &Number) -> Option<Self> {
let limit = normalized_number(limit);
limit.as_f64()?;
let value = exact(&limit).filter(|value| decimal(value).as_ref() == Some(&limit));
Some(Self { limit, value })
}
/// The rational the divisor denotes, when this build holds it.
fn exact_value(&self) -> Option<&BigFraction> {
self.value.as_ref()
}
pub(crate) fn to_number(&self) -> Number {
self.limit.clone()
}
/// Whether every value the divisor admits is whole, however the validator reads it.
pub(crate) fn admits_only_whole(&self) -> bool {
matches!(self.kind(), DivisorKind::Whole | DivisorKind::WholeLossy)
}
fn kind(&self) -> DivisorKind {
divisor_kind(&self.limit)
}
/// Whether `value` is a multiple, as the validator decides it.
pub(crate) fn divides(&self, value: &Number) -> bool {
satisfies_multiple_of(&self.limit, value)
}
/// Whether every multiple of `other` is also a multiple of this divisor. Only meaningful for
/// divisors taking the same arithmetic, which callers check.
pub(crate) fn divides_divisor(&self, other: &Self) -> bool {
let (Some(mine), Some(theirs)) = (self.exact_value(), other.exact_value()) else {
return false;
};
(theirs / mine.clone()).denom().is_none_or(One::is_one)
}
/// Whether one divisor may stand in for the other. A whole divisor within `f64` keeps integer
/// modulo in both builds while a fractional one goes through rational division, so unlike kinds
/// disagree on instances past that precision even under arbitrary precision.
#[cfg_attr(feature = "arbitrary-precision", allow(clippy::unused_self))]
pub(crate) fn shares_arithmetic(&self, other: &Self) -> bool {
#[cfg(feature = "arbitrary-precision")]
{
let _ = other;
true
}
#[cfg(not(feature = "arbitrary-precision"))]
{
self.kind() == other.kind()
}
}
/// The smallest value both divisors admit, or `None` when no divisor the validator reads the
/// same way spells it.
pub(crate) fn checked_lcm(&self, other: &Self) -> Option<Self> {
// For reduced fractions `lcm(p/q, r/s)` is `lcm(p, r) / gcd(q, s)`.
let (mine, theirs) = (self.exact_value()?, other.exact_value()?);
let numerator = mine.numer()?.lcm(theirs.numer()?);
let denominator = mine.denom()?.gcd(theirs.denom()?);
let combined = Self::new(&decimal(&BigFraction::new(numerator, denominator))?)?;
// A spelling the validator reads as a different number cannot stand for the pair, and nor
// can one it reads with different arithmetic.
(combined.exact_value().is_some()
&& combined.shares_arithmetic(self)
&& combined.shares_arithmetic(other))
.then_some(combined)
}
/// Whether any multiple lies within the interval. An open end always leaves room for one.
pub(crate) fn admits_between(
&self,
minimum: Option<&super::BoundNumber>,
maximum: Option<&super::BoundNumber>,
) -> bool {
let Some(step) = self.exact_value() else {
return true;
};
let (Some(low), Some((maximum, high))) = (
minimum.and_then(|bound| exact(&bound.to_number())),
maximum.and_then(|bound| Some((bound, exact(&bound.to_number())?))),
) else {
return true;
};
// The first multiple at or above the lower end. Snapping has already pulled an excluded
// end onto the next multiple wherever it could, and where it could not this leaf is kept
// rather than called empty.
let candidate = step * (low / step.clone()).ceil();
if candidate > high {
return false;
}
maximum.is_inclusive() || candidate < high
}
/// This divisor with the factors `other` already supplies removed, or `None` when nothing can
/// go. Only factors `other` carries at the same power may, or the pair would admit more.
/// e.g. 6 beside 2^52 => 3 (the twos are already there)
/// 4 beside 6 => None (6 has one two, 4 needs both)
pub(crate) fn without_factors_of(&self, other: &Self) -> Option<Self> {
let (mine, theirs) = (self.whole()?, other.whole()?);
// The largest divisor of `mine` built only from primes `theirs` has.
let (mut shared, mut rest) = (fraction::BigUint::one(), mine.clone());
loop {
let common = rest.gcd(theirs);
if common.is_one() {
break;
}
shared *= &common;
rest /= &common;
}
if shared.is_one() || !(theirs % &shared).is_zero() {
return None;
}
Self::new(&decimal(&BigFraction::new(rest, fraction::BigUint::one()))?)
.filter(|stripped| stripped.exact_value().is_some())
}
/// The divisor as a whole number, when it is one.
fn whole(&self) -> Option<&fraction::BigUint> {
let value = self.exact_value()?;
value.denom()?.is_one().then(|| value.numer()).flatten()
}
/// The first multiple at or past `bound` in `direction`, as a bound admitting it. `None` when no
/// decimal spells that multiple.
pub(crate) fn multiple_beyond(
&self,
bound: &super::BoundNumber,
direction: super::Round,
) -> Option<super::BoundNumber> {
let step = self.exact_value()?;
let spelling = bound.to_number();
let limit = exact(&spelling).filter(|limit| decimal(limit).as_ref() == Some(&spelling))?;
let steps = &limit / step.clone();
let steps = match direction {
super::Round::Up => steps.ceil(),
super::Round::Down => steps.floor(),
};
let mut candidate = step * steps;
// An end the interval excludes cannot keep a multiple sitting on it.
if !bound.is_inclusive() && candidate == limit {
candidate = match direction {
super::Round::Up => step + candidate,
super::Round::Down => candidate - step.clone(),
};
}
let snapped = decimal(&candidate)?;
// A spelling the validator reads as a different number would move the end, not pin it.
(exact(&snapped).as_ref() == Some(&candidate))
.then(|| super::BoundNumber::new(&snapped, true))
}
/// Whether every value is a multiple, which any other divisor already implies.
pub(crate) fn is_identity(&self) -> bool {
self.exact_value().is_some_and(One::is_one)
}
/// Whether every whole value is a multiple, which leaves the divisor no work on the integers.
pub(crate) fn is_vacuous_over_integers(&self) -> bool {
matches!(self.integer_fold(), IntegerFold::Vacuous)
}
/// The divisor as exact integer arithmetic reads it, when that matches the validator.
pub(crate) fn exact_integer(&self) -> Option<BoundInteger> {
match self.integer_fold() {
IntegerFold::Divisor(divisor) => Some(divisor),
IntegerFold::Vacuous | IntegerFold::Unfaithful => None,
}
}
/// What this divisor becomes over the integers. A whole divisor keeps its own arithmetic; a
/// fractional one only folds when every integer already qualifies, since the numerator would
/// otherwise move it onto integer modulo.
fn integer_fold(&self) -> IntegerFold {
let Some(numerator) = self
.exact_value()
.and_then(BigFraction::numer)
.and_then(|numerator| numerator.to_string().parse().ok())
.and_then(|numerator: Number| BoundInteger::from_number(&numerator))
else {
return IntegerFold::Unfaithful;
};
if numerator.is_one() {
return IntegerFold::Vacuous;
}
// Integer progressions are reasoned about exactly, which past `f64` precision is not how
// the validator reads the divisor.
match self.kind() {
DivisorKind::Whole if numerator.is_exact_in_f64() => IntegerFold::Divisor(numerator),
DivisorKind::Whole | DivisorKind::WholeLossy | DivisorKind::Fractional => {
IntegerFold::Unfaithful
}
}
}
}
impl PartialOrd for BoundRational {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for BoundRational {
fn cmp(&self, other: &Self) -> Ordering {
// A divisor with no rational still needs a place in the order, and its spelling is all
// there is to sort it by.
self.value
.cmp(&other.value)
.then_with(|| self.limit.to_string().cmp(&other.limit.to_string()))
}
}
/// The exact rational a JSON number denotes.
fn exact(number: &Number) -> Option<BigFraction> {
#[cfg(feature = "arbitrary-precision")]
{
if let Some(value) = jsonschema_value::numeric::bignum::try_parse_bigint(number) {
return Some(BigFraction::from(value));
}
if let Some(value) = jsonschema_value::numeric::bignum::try_parse_bigfraction(number) {
return Some(value);
}
}
number.as_f64().map(BigFraction::from)
}
/// `value` written as a decimal JSON number, or `None` when no finite decimal spells it.
fn decimal(value: &BigFraction) -> Option<Number> {
let denominator = value.denom()?;
// Scaling by ten clears one factor of two and one of five, so a finite decimal exists exactly
// when those are the denominator's only prime factors, and the wider power decides how many
// places it takes.
let (mut rest, mut twos, mut fives) = (denominator.clone(), 0_u32, 0_u32);
let (two, five) = (fraction::BigUint::from(2_u8), fraction::BigUint::from(5_u8));
while (&rest % &two).is_zero() {
rest /= &two;
twos += 1;
}
while (&rest % &five).is_zero() {
rest /= &five;
fives += 1;
}
debug_assert!(
rest.is_one(),
"a JSON number's denominator is a power of ten"
);
let places = twos.max(fives) as usize;
format!("{value:.places$}").parse().ok()
}