1
//! Hash-able floating-point wrappers, percentage values, and CSS size
2
//! metric types used by the CSS property system.
3

            
4
use core::fmt;
5
use std::num::ParseFloatError;
6

            
7
use crate::corety::AzString;
8

            
9
/// Multiplier for floating point accuracy. Elements such as px or %
10
/// are only accurate until a certain number of decimal points, therefore
11
/// they have to be casted to isizes in order to make the f32 values
12
/// hash-able: Css has a relatively low precision here, roughly 3 digits, i.e
13
/// `1.001 == 1.0`
14
pub const FP_PRECISION_MULTIPLIER: f32 = 1000.0;
15
const FP_PRECISION_MULTIPLIER_CONST: isize = FP_PRECISION_MULTIPLIER as isize;
16

            
17
/// Wrapper around FloatValue, represents a percentage instead
18
/// of just being a regular floating-point value, i.e `5` = `5%`
19
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
20
#[repr(C)]
21
pub struct PercentageValue {
22
    number: FloatValue,
23
}
24

            
25
impl_option!(
26
    PercentageValue,
27
    OptionPercentageValue,
28
    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
29
);
30

            
31
impl fmt::Display for PercentageValue {
32
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
33
        write!(f, "{}%", self.normalized() * 100.0)
34
    }
35
}
36

            
37
impl PercentageValue {
38
    /// Same as `PercentageValue::new()`, but only accepts whole numbers.
39
    /// Uses isize arithmetic to avoid floating-point in const context.
40
    #[inline]
41
7010
    pub const fn const_new(value: isize) -> Self {
42
7010
        Self {
43
7010
            number: FloatValue::const_new(value),
44
7010
        }
45
7010
    }
46

            
47
    /// Creates a PercentageValue from a fractional number in const context.
48
    ///
49
    /// # Arguments
50
    /// * `pre_comma` - The integer part (e.g., 100 for 100.5%)
51
    /// * `post_comma` - The fractional part as digits (e.g., 5 for 0.5%)
52
    ///
53
    /// # Examples
54
    /// ```
55
    /// // 100% = const_new_fractional(100, 0)
56
    /// // 50.5% = const_new_fractional(50, 5)
57
    /// ```
58
    #[inline]
59
    pub const fn const_new_fractional(pre_comma: isize, post_comma: isize) -> Self {
60
        Self {
61
            number: FloatValue::const_new_fractional(pre_comma, post_comma),
62
        }
63
    }
64

            
65
    #[inline]
66
494
    pub fn new(value: f32) -> Self {
67
494
        Self {
68
494
            number: value.into(),
69
494
        }
70
494
    }
71

            
72
    // NOTE: no get() function, to avoid confusion with "150%"
73

            
74
    #[inline]
75
7572
    pub fn normalized(&self) -> f32 {
76
7572
        self.number.get() / 100.0
77
7572
    }
78

            
79
    #[inline]
80
    pub fn interpolate(&self, other: &Self, t: f32) -> Self {
81
        Self {
82
            number: self.number.interpolate(&other.number, t),
83
        }
84
    }
85
}
86

            
87
/// Wrapper around an f32 value that is internally casted to an isize,
88
/// in order to provide hash-ability (to avoid numerical instability).
89
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
90
#[repr(C)]
91
pub struct FloatValue {
92
    pub(crate) number: isize,
93
}
94

            
95
impl fmt::Display for FloatValue {
96
30460
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
97
30460
        write!(f, "{}", self.get())
98
30460
    }
99
}
100

            
101
impl ::core::fmt::Debug for FloatValue {
102
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
103
        write!(f, "{}", self)
104
    }
105
}
106

            
107
impl Default for FloatValue {
108
1929
    fn default() -> Self {
109
        const DEFAULT_FLV: FloatValue = FloatValue::const_new(0);
110
1929
        DEFAULT_FLV
111
1929
    }
112
}
113

            
114
impl FloatValue {
115
    /// Same as `FloatValue::new()`, but only accepts whole numbers.
116
    /// Uses isize arithmetic to avoid floating-point in const context.
117
    #[inline]
118
140620
    pub const fn const_new(value: isize) -> Self {
119
140620
        Self {
120
140620
            number: value * FP_PRECISION_MULTIPLIER_CONST,
121
140620
        }
122
140620
    }
123

            
124
    /// Creates a FloatValue from a fractional number in const context.
125
    ///
126
    /// This uses integer arithmetic to represent fractional values like 1.5, 0.83, etc.
127
    /// in const context without relying on f32 operations.
128
    ///
129
    /// The function automatically detects the number of decimal places in `post_comma`
130
    /// and supports up to 3 decimal places. If more digits are provided, only the first
131
    /// 3 are used (truncation, not rounding).
132
    ///
133
    /// # Arguments
134
    /// * `pre_comma` - The integer part (e.g., 1 for 1.5)
135
    /// * `post_comma` - The fractional part as digits (e.g., 5 for 0.5, 52 for 0.52, 523 for 0.523)
136
    ///
137
    /// # Examples
138
    /// ```
139
    /// // 1.5 = const_new_fractional(1, 5)
140
    /// // 1.52 = const_new_fractional(1, 52)
141
    /// // 1.523 = const_new_fractional(1, 523)
142
    /// // 0.83 = const_new_fractional(0, 83)
143
    /// // 1.17 = const_new_fractional(1, 17)
144
    /// // 2.123456 -> 2.123 (truncated to 3 decimal places)
145
    /// ```
146
    #[inline]
147
48
    pub const fn const_new_fractional(pre_comma: isize, post_comma: isize) -> Self {
148
        // Get absolute value for digit counting
149
48
        let abs_post = if post_comma < 0 {
150
2
            -post_comma
151
        } else {
152
46
            post_comma
153
        };
154

            
155
        // Determine the number of digits and extract only the first 3
156
        // Note: We limit to values that fit in 32-bit isize for WASM compatibility
157
48
        let (normalized_post, divisor) = if abs_post < 10 {
158
            // 1 digit: 5 → 0.5
159
15
            (abs_post, 10)
160
33
        } else if abs_post < 100 {
161
            // 2 digits: 83 → 0.83
162
21
            (abs_post, 100)
163
12
        } else if abs_post < 1000 {
164
            // 3 digits: 523 → 0.523
165
7
            (abs_post, 1000)
166
5
        } else if abs_post < 10000 {
167
            // 4+ digits: take first 3 (e.g., 5234 → 523 → 0.523)
168
1
            (abs_post / 10, 1000)
169
4
        } else if abs_post < 100000 {
170
1
            (abs_post / 100, 1000)
171
3
        } else if abs_post < 1000000 {
172
1
            (abs_post / 1000, 1000)
173
2
        } else if abs_post < 10000000 {
174
1
            (abs_post / 10000, 1000)
175
1
        } else if abs_post < 100000000 {
176
            (abs_post / 100000, 1000)
177
1
        } else if abs_post < 1000000000 {
178
            (abs_post / 1000000, 1000)
179
        } else {
180
            // For very large values (>= 1 billion), cap at reasonable precision
181
            // This ensures compatibility with 32-bit isize on WASM
182
1
            (abs_post / 10000000, 1000)
183
        };
184

            
185
        // Calculate fractional part
186
48
        let fractional_part = normalized_post * (FP_PRECISION_MULTIPLIER_CONST / divisor);
187

            
188
        // Apply sign: if post_comma is negative, negate the fractional part
189
48
        let signed_fractional = if post_comma < 0 {
190
2
            -fractional_part
191
        } else {
192
46
            fractional_part
193
        };
194

            
195
        // For negative pre_comma, the fractional part should also be negative
196
        // E.g., -1.5 = -1 + (-0.5), not -1 + 0.5
197
48
        let final_fractional = if pre_comma < 0 && post_comma >= 0 {
198
2
            -signed_fractional
199
        } else {
200
46
            signed_fractional
201
        };
202

            
203
48
        Self {
204
48
            number: pre_comma * FP_PRECISION_MULTIPLIER_CONST + final_fractional,
205
48
        }
206
48
    }
207

            
208
    #[inline]
209
930814
    pub fn new(value: f32) -> Self {
210
930814
        Self {
211
930814
            number: (value * FP_PRECISION_MULTIPLIER) as isize,
212
930814
        }
213
930814
    }
214

            
215
    #[inline]
216
983894
    pub fn get(&self) -> f32 {
217
983894
        self.number as f32 / FP_PRECISION_MULTIPLIER
218
983894
    }
219

            
220
    /// Returns the raw encoded `isize` (the f32 value scaled by
221
    /// `FP_PRECISION_MULTIPLIER`). Exposed so external callers can
222
    /// round-trip the value through the compact-cache encoding without
223
    /// re-multiplying through f32.
224
    #[inline]
225
    pub fn number(&self) -> isize {
226
        self.number
227
    }
228

            
229
    #[inline]
230
    pub fn interpolate(&self, other: &Self, t: f32) -> Self {
231
        let self_val_f32 = self.get();
232
        let other_val_f32 = other.get();
233
        let interpolated = self_val_f32 + ((other_val_f32 - self_val_f32) * t);
234
        Self::new(interpolated)
235
    }
236
}
237

            
238
impl From<f32> for FloatValue {
239
    #[inline]
240
494
    fn from(val: f32) -> Self {
241
494
        Self::new(val)
242
494
    }
243
}
244

            
245
/// Enum representing the metric associated with a number (px, pt, em, etc.)
246
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
247
#[repr(C)]
248
#[derive(Default)]
249
pub enum SizeMetric {
250
    #[default]
251
    Px,
252
    Pt,
253
    Em,
254
    Rem,
255
    In,
256
    Cm,
257
    Mm,
258
    Percent,
259
    /// Viewport width: 1vw = 1% of viewport width
260
    Vw,
261
    /// Viewport height: 1vh = 1% of viewport height
262
    Vh,
263
    /// Viewport minimum: 1vmin = 1% of smaller viewport dimension
264
    Vmin,
265
    /// Viewport maximum: 1vmax = 1% of larger viewport dimension
266
    Vmax,
267
}
268

            
269

            
270
impl fmt::Display for SizeMetric {
271
30460
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
272
        use self::SizeMetric::*;
273
30460
        match self {
274
30255
            Px => write!(f, "px"),
275
            Pt => write!(f, "pt"),
276
137
            Em => write!(f, "em"),
277
            Rem => write!(f, "rem"),
278
            In => write!(f, "in"),
279
            Cm => write!(f, "cm"),
280
            Mm => write!(f, "mm"),
281
26
            Percent => write!(f, "%"),
282
            Vw => write!(f, "vw"),
283
42
            Vh => write!(f, "vh"),
284
            Vmin => write!(f, "vmin"),
285
            Vmax => write!(f, "vmax"),
286
        }
287
30460
    }
288
}
289

            
290
266
pub fn parse_float_value(input: &str) -> Result<FloatValue, ParseFloatError> {
291
266
    Ok(FloatValue::new(input.trim().parse::<f32>()?))
292
266
}
293

            
294
#[derive(Clone, PartialEq, Eq)]
295
#[repr(C, u8)]
296
pub enum PercentageParseError {
297
    ValueParseErr(crate::props::basic::error::ParseFloatError),
298
    NoPercentSign,
299
    InvalidUnit(AzString),
300
}
301

            
302
impl_debug_as_display!(PercentageParseError);
303

            
304
impl From<ParseFloatError> for PercentageParseError {
305
    fn from(e: ParseFloatError) -> Self {
306
        PercentageParseError::ValueParseErr(crate::props::basic::error::ParseFloatError::from(e))
307
    }
308
}
309

            
310
impl_display! { PercentageParseError, {
311
    ValueParseErr(e) => format!("\"{}\"", e),
312
    NoPercentSign => format!("No percent sign after number"),
313
    InvalidUnit(u) => format!("Error parsing percentage: invalid unit \"{}\"", u.as_str()),
314
}}
315

            
316
#[derive(Debug, Clone, PartialEq, Eq)]
317
#[repr(C, u8)]
318
pub enum PercentageParseErrorOwned {
319
    ValueParseErr(crate::props::basic::error::ParseFloatError),
320
    NoPercentSign,
321
    InvalidUnit(AzString),
322
}
323

            
324
impl PercentageParseError {
325
    pub fn to_contained(&self) -> PercentageParseErrorOwned {
326
        match self {
327
            Self::ValueParseErr(e) => PercentageParseErrorOwned::ValueParseErr(*e),
328
            Self::NoPercentSign => PercentageParseErrorOwned::NoPercentSign,
329
            Self::InvalidUnit(u) => PercentageParseErrorOwned::InvalidUnit(u.clone()),
330
        }
331
    }
332
}
333

            
334
impl PercentageParseErrorOwned {
335
    pub fn to_shared(&self) -> PercentageParseError {
336
        match self {
337
            Self::ValueParseErr(e) => PercentageParseError::ValueParseErr(*e),
338
            Self::NoPercentSign => PercentageParseError::NoPercentSign,
339
            Self::InvalidUnit(u) => PercentageParseError::InvalidUnit(u.clone()),
340
        }
341
    }
342
}
343

            
344
/// Parse "1.2" or "120%" (similar to parse_pixel_value)
345
221
pub fn parse_percentage_value(input: &str) -> Result<PercentageValue, PercentageParseError> {
346
221
    let input = input.trim();
347

            
348
221
    if input.is_empty() {
349
1
        return Err(PercentageParseError::ValueParseErr(
350
1
            crate::props::basic::error::ParseFloatError::from("empty string".parse::<f32>().unwrap_err()),
351
1
        ));
352
220
    }
353

            
354
220
    let mut split_pos = 0;
355
220
    let mut found_numeric = false;
356
731
    for (idx, ch) in input.char_indices() {
357
731
        if ch.is_numeric() || ch == '.' || ch == '-' {
358
639
            split_pos = idx;
359
639
            found_numeric = true;
360
639
        }
361
    }
362

            
363
220
    if !found_numeric {
364
2
        return Err(PercentageParseError::ValueParseErr(
365
2
            crate::props::basic::error::ParseFloatError::from("no numeric value".parse::<f32>().unwrap_err()),
366
2
        ));
367
218
    }
368

            
369
218
    split_pos += 1;
370

            
371
218
    let unit = input[split_pos..].trim();
372
218
    let mut number = input[..split_pos]
373
218
        .trim()
374
218
        .parse::<f32>()
375
218
        .map_err(|e| PercentageParseError::ValueParseErr(crate::props::basic::error::ParseFloatError::from(e)))?;
376

            
377
218
    match unit {
378
218
        "" => {
379
157
            number *= 100.0;
380
157
        } // 0.5 => 50%
381
61
        "%" => {} // 50% => PercentageValue(50.0)
382
21
        other => {
383
21
            return Err(PercentageParseError::InvalidUnit(other.to_string().into()));
384
        }
385
    }
386

            
387
197
    Ok(PercentageValue::new(number))
388
221
}
389

            
390
#[cfg(all(test, feature = "parser"))]
391
mod tests {
392
    use super::*;
393

            
394
    #[test]
395
1
    fn test_parse_float_value() {
396
1
        assert_eq!(parse_float_value("10").unwrap().get(), 10.0);
397
1
        assert_eq!(parse_float_value("2.5").unwrap().get(), 2.5);
398
1
        assert_eq!(parse_float_value("-50.2").unwrap().get(), -50.2);
399
1
        assert_eq!(parse_float_value("  0  ").unwrap().get(), 0.0);
400
1
        assert!(parse_float_value("10a").is_err());
401
1
        assert!(parse_float_value("").is_err());
402
1
    }
403

            
404
    #[test]
405
1
    fn test_parse_percentage_value() {
406
        // With percent sign
407
1
        assert_eq!(parse_percentage_value("50%").unwrap().normalized(), 0.5);
408
1
        assert_eq!(parse_percentage_value("120%").unwrap().normalized(), 1.2);
409
1
        assert_eq!(parse_percentage_value("-25%").unwrap().normalized(), -0.25);
410
1
        assert_eq!(
411
1
            parse_percentage_value("  75.5%  ").unwrap().normalized(),
412
            0.755
413
        );
414

            
415
        // As a ratio
416
1
        assert!((parse_percentage_value("0.5").unwrap().normalized() - 0.5).abs() < 1e-6);
417
1
        assert!((parse_percentage_value("1.2").unwrap().normalized() - 1.2).abs() < 1e-6);
418
1
        assert!((parse_percentage_value("1").unwrap().normalized() - 1.0).abs() < 1e-6);
419

            
420
        // Errors
421
1
        assert!(matches!(
422
1
            parse_percentage_value("50px").err().unwrap(),
423
            PercentageParseError::InvalidUnit(_)
424
        ));
425
1
        assert!(parse_percentage_value("fifty%").is_err());
426
1
        assert!(parse_percentage_value("").is_err());
427
1
    }
428

            
429
    #[test]
430
1
    fn test_const_new_fractional_single_digit() {
431
        // Single digit post_comma (1 decimal place)
432
1
        let val = FloatValue::const_new_fractional(1, 5);
433
1
        assert_eq!(val.get(), 1.5);
434

            
435
1
        let val = FloatValue::const_new_fractional(0, 5);
436
1
        assert_eq!(val.get(), 0.5);
437

            
438
1
        let val = FloatValue::const_new_fractional(2, 3);
439
1
        assert_eq!(val.get(), 2.3);
440

            
441
1
        let val = FloatValue::const_new_fractional(0, 0);
442
1
        assert_eq!(val.get(), 0.0);
443

            
444
1
        let val = FloatValue::const_new_fractional(10, 9);
445
1
        assert_eq!(val.get(), 10.9);
446
1
    }
447

            
448
    #[test]
449
1
    fn test_const_new_fractional_two_digits() {
450
        // Two digits post_comma (2 decimal places)
451
1
        let val = FloatValue::const_new_fractional(0, 83);
452
1
        assert!((val.get() - 0.83).abs() < 0.001);
453

            
454
1
        let val = FloatValue::const_new_fractional(1, 17);
455
1
        assert!((val.get() - 1.17).abs() < 0.001);
456

            
457
1
        let val = FloatValue::const_new_fractional(1, 52);
458
1
        assert!((val.get() - 1.52).abs() < 0.001);
459

            
460
1
        let val = FloatValue::const_new_fractional(0, 33);
461
1
        assert!((val.get() - 0.33).abs() < 0.001);
462

            
463
1
        let val = FloatValue::const_new_fractional(2, 67);
464
1
        assert!((val.get() - 2.67).abs() < 0.001);
465

            
466
1
        let val = FloatValue::const_new_fractional(0, 10);
467
1
        assert!((val.get() - 0.10).abs() < 0.001);
468

            
469
1
        let val = FloatValue::const_new_fractional(0, 99);
470
1
        assert!((val.get() - 0.99).abs() < 0.001);
471
1
    }
472

            
473
    #[test]
474
1
    fn test_const_new_fractional_three_digits() {
475
        // Three digits post_comma (3 decimal places)
476
1
        let val = FloatValue::const_new_fractional(1, 523);
477
1
        assert!((val.get() - 1.523).abs() < 0.001);
478

            
479
1
        let val = FloatValue::const_new_fractional(0, 123);
480
1
        assert!((val.get() - 0.123).abs() < 0.001);
481

            
482
1
        let val = FloatValue::const_new_fractional(2, 999);
483
1
        assert!((val.get() - 2.999).abs() < 0.001);
484

            
485
1
        let val = FloatValue::const_new_fractional(0, 100);
486
1
        assert!((val.get() - 0.100).abs() < 0.001);
487

            
488
1
        let val = FloatValue::const_new_fractional(5, 1);
489
1
        assert!((val.get() - 5.1).abs() < 0.001);
490
1
    }
491

            
492
    #[test]
493
1
    fn test_const_new_fractional_truncation() {
494
        // More than 3 digits should be truncated (not rounded)
495

            
496
        // 4 digits: 5234 → 523 → 0.523
497
1
        let val = FloatValue::const_new_fractional(0, 5234);
498
1
        assert!((val.get() - 0.523).abs() < 0.001);
499

            
500
        // 5 digits: 12345 → 123 → 0.123
501
1
        let val = FloatValue::const_new_fractional(1, 12345);
502
1
        assert!((val.get() - 1.123).abs() < 0.001);
503

            
504
        // 6 digits: 123456 → 123 → 1.123
505
1
        let val = FloatValue::const_new_fractional(1, 123456);
506
1
        assert!((val.get() - 1.123).abs() < 0.001);
507

            
508
        // 7 digits: 9876543 → 987 → 0.987
509
1
        let val = FloatValue::const_new_fractional(0, 9876543);
510
1
        assert!((val.get() - 0.987).abs() < 0.001);
511

            
512
        // 10 digits
513
1
        let val = FloatValue::const_new_fractional(2, 1234567890);
514
1
        assert!((val.get() - 2.123).abs() < 0.001);
515
1
    }
516

            
517
    #[test]
518
1
    fn test_const_new_fractional_negative() {
519
        // Negative pre_comma values
520
1
        let val = FloatValue::const_new_fractional(-1, 5);
521
1
        assert_eq!(val.get(), -1.5);
522

            
523
1
        let val = FloatValue::const_new_fractional(0, 83);
524
1
        assert!((val.get() - 0.83).abs() < 0.001);
525

            
526
1
        let val = FloatValue::const_new_fractional(-2, 123);
527
1
        assert!((val.get() - -2.123).abs() < 0.001);
528

            
529
        // Negative post_comma (unusual case - treated as negative fractional part)
530
1
        let val = FloatValue::const_new_fractional(1, -5);
531
1
        assert_eq!(val.get(), 0.5); // 1 + (-0.5) = 0.5
532

            
533
1
        let val = FloatValue::const_new_fractional(0, -50);
534
1
        assert!((val.get() - -0.5).abs() < 0.001); // 0 + (-0.5) = -0.5
535
1
    }
536

            
537
    #[test]
538
1
    fn test_const_new_fractional_edge_cases() {
539
        // Zero
540
1
        let val = FloatValue::const_new_fractional(0, 0);
541
1
        assert_eq!(val.get(), 0.0);
542

            
543
        // Large integer part
544
1
        let val = FloatValue::const_new_fractional(100, 5);
545
1
        assert_eq!(val.get(), 100.5);
546

            
547
1
        let val = FloatValue::const_new_fractional(1000, 99);
548
1
        assert!((val.get() - 1000.99).abs() < 0.001);
549

            
550
        // Maximum precision (3 digits)
551
1
        let val = FloatValue::const_new_fractional(0, 999);
552
1
        assert!((val.get() - 0.999).abs() < 0.001);
553

            
554
        // Small fractional values
555
1
        let val = FloatValue::const_new_fractional(1, 1);
556
1
        assert!((val.get() - 1.1).abs() < 0.001);
557

            
558
1
        let val = FloatValue::const_new_fractional(1, 10);
559
1
        assert!((val.get() - 1.10).abs() < 0.001);
560
1
    }
561

            
562
    #[test]
563
1
    fn test_const_new_fractional_ua_css_values() {
564
        // Test actual values used in ua_css.rs
565

            
566
        // H1: 2em
567
1
        let val = FloatValue::const_new_fractional(2, 0);
568
1
        assert_eq!(val.get(), 2.0);
569

            
570
        // H2: 1.5em
571
1
        let val = FloatValue::const_new_fractional(1, 5);
572
1
        assert_eq!(val.get(), 1.5);
573

            
574
        // H3: 1.17em
575
1
        let val = FloatValue::const_new_fractional(1, 17);
576
1
        assert!((val.get() - 1.17).abs() < 0.001);
577

            
578
        // H4: 1em
579
1
        let val = FloatValue::const_new_fractional(1, 0);
580
1
        assert_eq!(val.get(), 1.0);
581

            
582
        // H5: 0.83em
583
1
        let val = FloatValue::const_new_fractional(0, 83);
584
1
        assert!((val.get() - 0.83).abs() < 0.001);
585

            
586
        // H6: 0.67em
587
1
        let val = FloatValue::const_new_fractional(0, 67);
588
1
        assert!((val.get() - 0.67).abs() < 0.001);
589

            
590
        // Margins: 0.67em
591
1
        let val = FloatValue::const_new_fractional(0, 67);
592
1
        assert!((val.get() - 0.67).abs() < 0.001);
593

            
594
        // Margins: 0.83em
595
1
        let val = FloatValue::const_new_fractional(0, 83);
596
1
        assert!((val.get() - 0.83).abs() < 0.001);
597

            
598
        // Margins: 1.33em
599
1
        let val = FloatValue::const_new_fractional(1, 33);
600
1
        assert!((val.get() - 1.33).abs() < 0.001);
601

            
602
        // Margins: 1.67em
603
1
        let val = FloatValue::const_new_fractional(1, 67);
604
1
        assert!((val.get() - 1.67).abs() < 0.001);
605

            
606
        // Margins: 2.33em
607
1
        let val = FloatValue::const_new_fractional(2, 33);
608
1
        assert!((val.get() - 2.33).abs() < 0.001);
609
1
    }
610

            
611
    #[test]
612
1
    fn test_const_new_fractional_consistency() {
613
        // Verify consistency between const_new_fractional and new()
614

            
615
1
        let const_val = FloatValue::const_new_fractional(1, 5);
616
1
        let runtime_val = FloatValue::new(1.5);
617
1
        assert_eq!(const_val.get(), runtime_val.get());
618

            
619
1
        let const_val = FloatValue::const_new_fractional(0, 83);
620
1
        let runtime_val = FloatValue::new(0.83);
621
1
        assert!((const_val.get() - runtime_val.get()).abs() < 0.001);
622

            
623
1
        let const_val = FloatValue::const_new_fractional(1, 523);
624
1
        let runtime_val = FloatValue::new(1.523);
625
1
        assert!((const_val.get() - runtime_val.get()).abs() < 0.001);
626

            
627
1
        let const_val = FloatValue::const_new_fractional(2, 99);
628
1
        let runtime_val = FloatValue::new(2.99);
629
1
        assert!((const_val.get() - runtime_val.get()).abs() < 0.001);
630
1
    }
631
}