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
/*
Copyright 2025 Owain Davies
SPDX-License-Identifier: Apache-2.0 OR MIT
*/
// Equivalent to 2.0f64.powi(n) or Java's Double.parseDouble("0x1p" + n) for
// any n (i32).
pub(crate) fn fpow2(n: i32) -> f64 {
if (-1022..=1023).contains(&n) {
// Normal
f64::from_bits(((1023 + n as i64) << 52) as u64)
} else if (-1074..-1022).contains(&n) {
// Subnormal
f64::from_bits(1u64 << (n + 1074))
} else if n < -1074 {
// Underflow to zero
0.0
} else {
// n > 1023: overflow
f64::INFINITY
}
}
pub(crate) trait MathOps {
// NOTE: next_up(), next_down() are not available in core and std until 1.86
fn next_up_(self) -> Self;
fn next_down_(self) -> Self;
// fn sqrt(self) -> Self;
// fn powf(self, n: Self) -> Self;
}
impl MathOps for f64 {
fn next_up_(self) -> Self {
// if self.is_nan(), this returns self;
if self.is_nan() {
return self;
}
// if self is NEG_INFINITY, this returns MIN;
if self == f64::NEG_INFINITY {
return f64::MIN;
}
// if self is -TINY, this returns -0.0;
if self == -f64::from_bits(1) {
return -0.0;
}
// if self is -0.0 or +0.0, this returns TINY;
if self == 0.0 {
return f64::from_bits(1);
}
// if self is MAX or INFINITY, this returns INFINITY;
if self == f64::MAX || self == f64::INFINITY {
return f64::INFINITY;
}
// otherwise the unique least value greater than self is returned.
let bits = self.to_bits();
if self > 0.0 {
f64::from_bits(bits + 1)
} else {
f64::from_bits(bits - 1)
}
}
fn next_down_(self) -> Self {
// if self.is_nan(), this returns self;
if self.is_nan() {
return self;
}
// if self is INFINITY, this returns MAX;
if self == f64::INFINITY {
return f64::MAX;
}
// if self is TINY, this returns 0.0;
if self == f64::from_bits(1) {
return 0.0;
}
// if self is -0.0 or +0.0, this returns -TINY;
if self == 0.0 {
return -f64::from_bits(1);
}
// if self is MIN or NEG_INFINITY, this returns NEG_INFINITY;
if self == f64::MIN || self == f64::NEG_INFINITY {
return f64::NEG_INFINITY;
}
// otherwise the unique greatest value less than self is returned.
let bits = self.to_bits();
if self > 0.0 {
f64::from_bits(bits - 1)
} else {
f64::from_bits(bits + 1)
}
}
}
#[cfg(test)]
mod tests {
use super::MathOps;
#[test]
fn test_next_up() {
assert!(f64::NAN.next_up_().is_nan());
assert_eq!(f64::NEG_INFINITY.next_up_(), f64::MIN);
assert_eq!(f64::INFINITY.next_up_(), f64::INFINITY);
assert_eq!(0.0f64.next_up_(), f64::from_bits(1));
assert_eq!((-0.0f64).next_up_(), f64::from_bits(1));
assert_eq!((-f64::from_bits(1)).next_up_(), -0.0);
assert_eq!(1.0f64.next_up_(), 1.0 + f64::EPSILON);
assert_eq!(f64::MAX.next_up_(), f64::INFINITY);
}
#[test]
fn test_next_down() {
assert!(f64::NAN.next_down_().is_nan());
assert_eq!(f64::INFINITY.next_down_(), f64::MAX);
assert_eq!(f64::NEG_INFINITY.next_down_(), f64::NEG_INFINITY);
assert_eq!(0.0f64.next_down_(), -f64::from_bits(1));
assert_eq!((-0.0f64).next_down_(), -f64::from_bits(1));
assert_eq!(f64::from_bits(1).next_down_(), 0.0);
assert_eq!(f64::MIN.next_down_(), f64::NEG_INFINITY);
}
// The identity x.next_up() == -(-x).next_down() holds for all non-NaN x.
// When x is finite x == x.next_up().next_down() also holds.
// The identity x.next_down() == -(-x).next_up() holds for all non-NaN x.
// When x is finite x == x.next_down().next_up() also holds.
#[test]
fn test_identity() {
let test_values = [
0.1,
-0.1,
1.0,
-1.0,
100.0,
-100.0,
f64::MIN,
f64::MAX,
f64::NEG_INFINITY,
f64::INFINITY,
];
for &x in &test_values {
assert_eq!(x.next_up_(), -(-x).next_down_());
if x.is_finite() {
assert_eq!(x, x.next_up_().next_down_());
}
assert_eq!(x.next_down_(), -(-x).next_up_());
if x.is_finite() {
assert_eq!(x, x.next_down_().next_up_());
}
}
}
}