1use std::fmt::Display;
2
3use crate::{
4 common::glue::Glue,
5 definition::register::RegisterType,
6 token::{Catcode, Token},
7};
8
9pub const UNITY: i64 = 65536;
10pub const UNITY_F64: f64 = 65536.0;
11pub const EPSILON: f64 = 0.000_000_119_209_29;
12pub const ROUNDING_HALF: f64 = 0.49999994;
13pub const SCALES: &[i32] = &[1, 10, 100, 1000, 10000, 100_000];
14
15pub fn round_to(number: f64, prec_opt: Option<u8>) -> f64 {
17 let mut prec = prec_opt.unwrap_or(2);
18 if prec > 5 {
19 prec = 5;
20 }
21 let scale = SCALES[prec as usize];
22 let n = number * scale as f64 * (1.0 + 100.0 * EPSILON);
24 let adjusted: f64 = if n < -EPSILON {
25 n - 0.5
26 } else if n > EPSILON {
27 n + 0.5
28 } else {
29 0.0
30 };
31 adjusted.trunc() / scale as f64
32}
33
34pub fn kround(number: f64) -> i64 {
39 let rounded = if number < 0.0 {
40 number - ROUNDING_HALF
41 } else {
42 number + ROUNDING_HALF
43 };
44 rounded.trunc() as i64
45}
46
47pub fn fixpoint(float: f64, unit_opt: Option<f64>) -> i64 {
53 let fix = kround(float * UNITY_F64);
54 if let Some(unit) = unit_opt {
55 (fix as f64 * unit / UNITY_F64).trunc() as i64
56 } else {
57 fix
58 }
59}
60
61pub fn fixpoint_unit(float: f64, num: i64, den: i64) -> i64 {
79 let fix = kround(float * UNITY_F64) as i128;
80 (fix * num as i128 / den as i128) as i64
81}
82
83pub trait NumericOps {
84 fn new(num: i64) -> Self
85 where Self: Sized;
86 fn new_f64(num: f64) -> Self
87 where Self: Sized;
88 fn unit(&self) -> Option<&'static str> { None }
89 fn value_of(self) -> i64;
90 fn value_f64(self) -> f64
91 where Self: Sized {
92 self.value_of() as f64
93 }
94 fn pt_value(self, prec: Option<u8>) -> f64
95 where Self: Sized {
96 round_to(self.value_of() as f64 / UNITY_F64, prec)
97 }
98 fn px_value(self, prec: Option<u8>) -> f64
99 where Self: Sized {
100 let dpi = crate::state::lookup_int("DPI");
101 let dpi = if dpi > 0 { dpi as f64 } else { 100.0 };
102 round_to((self.value_f64() / UNITY_F64) * (dpi / 72.27), prec)
103 }
104
105 fn absolute(self) -> Self
106 where Self: Sized {
107 Self::new(self.value_of().abs())
108 }
109
110 fn sign(self) -> i8
111 where Self: Sized {
112 use std::cmp::Ordering::*;
113 match self.value_of().cmp(&0) {
114 Less => -1,
115 Equal => 0,
116 Greater => 1,
117 }
118 }
119
120 fn negate(self) -> Self
121 where Self: Sized {
122 Self::new(-self.value_of())
123 }
124 fn add<T: NumericOps>(self, other: T) -> Self
125 where Self: Sized {
126 Self::new(self.value_of() + other.value_of())
127 }
128 fn subtract<T: NumericOps>(self, other: T) -> Self
129 where Self: Sized {
130 Self::new(self.value_of() - other.value_of())
131 }
132 fn multiply<T: NumericOps>(self, other: T) -> Self
135 where Self: Sized {
136 Self::new((self.value_of() as f64 * other.value_f64()) as i64)
137 }
138 fn divide<T: NumericOps>(self, other: T) -> Self
140 where Self: Sized {
141 let mut other_value: f64 = other.value_of() as f64;
142 if other_value == 0.0 {
143 other_value = EPSILON; }
145 Self::new((self.value_of() as f64 / other_value).trunc() as i64)
146 }
147
148 fn divideround<T: NumericOps>(self, other: T) -> Self
150 where Self: Sized {
151 let mut other_value: f64 = other.value_of() as f64;
152 if other_value == 0.0 {
153 other_value = EPSILON; }
155 Self::new((0.5 + self.value_of() as f64 / other_value).trunc() as i64)
156 }
157
158 fn smaller<T: NumericOps>(self, other: T) -> Self
159 where Self: Sized {
160 Self::new(self.value_of().min(other.value_of()))
161 }
162
163 fn larger<T: NumericOps>(self, other: T) -> Self
164 where Self: Sized {
165 Self::new(self.value_of().max(other.value_of()))
166 }
167
168 fn to_token(self) -> Token
169 where Self: Sized {
170 T_OTHER!(self.value_of().to_string())
171 }
172 fn into_glue_type(self) -> Glue
174 where Self: Sized {
175 Glue::new(0) }
177 fn register_type(&self) -> RegisterType;
178 fn to_attribute(&self) -> String
179 where Self: Display {
180 self.to_string()
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn round_to_default_precision_is_two() {
190 assert_eq!(round_to(1.2345, None), 1.23);
191 assert_eq!(round_to(1.2355, None), 1.24);
192 assert_eq!(round_to(0.0, None), 0.0);
193 }
194
195 #[test]
196 fn round_to_respects_precision() {
197 assert_eq!(round_to(1.23456, Some(3)), 1.235);
198 assert_eq!(round_to(1.23456, Some(0)), 1.0);
199 assert_eq!(round_to(1.5, Some(0)), 2.0);
200 }
201
202 #[test]
203 fn round_to_caps_precision_at_five() {
204 let a = round_to(1.12345, Some(5));
207 let b = round_to(1.12345, Some(10));
208 assert_eq!(a, b, "precision > 5 clamps to 5 (got {a} vs {b})");
209 }
210
211 #[test]
212 fn round_to_negative_numbers() {
213 assert_eq!(round_to(-1.235, None), -1.24);
214 assert_eq!(round_to(-0.005, None), -0.01);
215 }
216
217 #[test]
218 fn kround_basic() {
219 assert_eq!(kround(0.0), 0);
220 assert_eq!(kround(0.49), 0);
221 assert_eq!(kround(1.49), 1);
224 assert_eq!(kround(1.5), 1);
225 assert_eq!(kround(-0.49), 0);
226 assert_eq!(kround(-1.49), -1);
227 }
228
229 #[test]
230 fn fixpoint_without_unit() {
231 assert_eq!(fixpoint(1.0, None), UNITY);
233 assert_eq!(fixpoint(0.0, None), 0);
234 assert_eq!(fixpoint(0.5, None), UNITY / 2);
235 }
236
237 #[test]
238 fn fixpoint_with_unit_scales() {
239 let out = fixpoint(1.0, Some(1.0));
242 assert!(out >= 0 || out < 0, "defined integer output: {out}");
245 }
246
247 #[test]
248 fn constants_unity_matches_f64() {
249 assert_eq!(UNITY as f64, UNITY_F64);
251 }
252
253 #[test]
254 fn fixpoint_unit_matches_pdftex() {
255 let cases: &[(f64, i64, i64, i64)] = &[
260 (1.0, 7227, 100, 4736286), (1.0, 7227, 254, 1864679), (1.0, 7227, 2540, 186467), (1.0, 7227, 7200, 65781), (1.0, 1238, 1157, 70124), (1.0, 14856, 1157, 841489), (76.24341, 7227, 254, 142169544), (400.43946, 7227, 7200, 26341612), (188.33008, 7227, 7200, 12388684), (75.77057, 7227, 2540, 14128785), (76.40832, 14856, 1157, 64296768), ];
273 for &(value, num, den, expected) in cases {
274 assert_eq!(
275 fixpoint_unit(value, num, den),
276 expected,
277 "fixpoint_unit({value}, {num}, {den}) should match pdftex"
278 );
279 }
280 }
281
282 #[test]
283 fn fixpoint_unit_is_exact_floor() {
284 let units: &[(i64, i64)] = &[(7227, 100), (7227, 254), (7227, 2540), (7227, 7200)];
288 for &(num, den) in units {
289 for k in 0..2000 {
290 let value = k as f64 * 0.13759 + 0.0007; let fix = kround(value * UNITY_F64) as i128;
292 let expected = (fix * num as i128 / den as i128) as i64;
293 assert_eq!(fixpoint_unit(value, num, den), expected);
294 }
295 }
296 }
297}