Skip to content

Commit f655f4c

Browse files
committed
implement fmin & fmax instructions
1 parent a3dd786 commit f655f4c

File tree

5 files changed

+238
-1
lines changed

5 files changed

+238
-1
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
* Bitwise compatible with Softfloat + passes all tests
1919
* Can simulate RISC-V extensions F & D
2020

21+
For a quick benchmark vs other Rust softfloat libraries, see [softfloat_bench].
22+
23+
[softfloat_bench]: https://github.com/HarryR/softfloat_bench/
2124
[RISC-V]: https://five-embeddev.com/riscv-user-isa-manual/Priv-v1.12/f.html
2225
[IEEE-754]: https://en.wikipedia.org/wiki/IEEE_754
2326
[C2Rust]: https://github.com/immunant/c2rust
@@ -54,7 +57,7 @@ You can directly access the underlying SoftFloat functions, like `f32_mulAdd` vi
5457
use softfloat_pure::softfloat::*;
5558
let a = float32_t::from_bits(0x...);
5659
let b = float32_t::from_bits(0x...);
57-
let x = f32_add(a,b, 0, 0);
60+
let x = f32_add(a, b, 0/*rounding_mode*/, 0/*detect_tininess*/);
5861
assert_eq!(x.0, 0x...); // result
5962
assert_eq!(x.1, 0); // flags
6063
```

src/fpu.rs

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,60 @@ impl FPU {
267267
{
268268
self.flagged(a.borrow().sqrt(rnd, self.detect_tininess))
269269
}
270+
271+
#[inline]
272+
#[must_use]
273+
pub fn max<F, T>(&mut self, a: T, b: T) -> F
274+
where
275+
F: Float,
276+
T: Borrow<F>,
277+
{
278+
let fs1_val = a.borrow();
279+
let fs2_val = b.borrow();
280+
let fs2_nan = fs2_val.is_nan();
281+
let fs1_nan = fs1_val.is_nan();
282+
if fs1_nan && fs2_nan {
283+
F::from_bits(F::DEFAULT_NAN)
284+
}
285+
else {
286+
let is_lt = self.lt_quiet::<F,&F>(fs2_val, fs1_val);
287+
let is_eq = self.eq::<F,&F>(fs2_val, fs1_val);
288+
let greater = is_lt || (is_eq && (fs1_val.is_positive() || !fs2_val.is_positive()));
289+
if greater || fs2_nan {
290+
F::from_bits(fs1_val.to_bits())
291+
}
292+
else {
293+
F::from_bits(fs2_val.to_bits())
294+
}
295+
}
296+
}
297+
298+
#[inline]
299+
#[must_use]
300+
pub fn min<F, T>(&mut self, a: T, b: T) -> F
301+
where
302+
F: Float,
303+
T: Borrow<F>,
304+
{
305+
let fs1_val = a.borrow();
306+
let fs2_val = b.borrow();
307+
let fs2_nan = fs2_val.is_nan();
308+
let fs1_nan = fs1_val.is_nan();
309+
if fs1_nan && fs2_nan {
310+
F::from_bits(F::DEFAULT_NAN)
311+
}
312+
else {
313+
let is_lt = self.lt_quiet::<F,&F>(fs1_val, fs2_val);
314+
let is_eq = self.eq::<F,&F>(fs1_val, fs2_val);
315+
let less = is_lt || (is_eq && !fs1_val.is_positive());
316+
if less || fs2_nan {
317+
F::from_bits(fs1_val.to_bits())
318+
}
319+
else {
320+
F::from_bits(fs2_val.to_bits())
321+
}
322+
}
323+
}
270324
}
271325

272326
impl FPU {
@@ -320,3 +374,176 @@ impl FPU {
320374
ui32_to_f64(a)
321375
}
322376
}
377+
378+
#[cfg(test)]
379+
mod tests {
380+
use super::*;
381+
382+
// Helper functions to create specific float values
383+
fn make_f32(v: f32) -> float32_t {
384+
float32_t::from_bits(v.to_bits())
385+
}
386+
387+
fn make_f64(v: f64) -> float64_t {
388+
float64_t::from_bits(v.to_bits())
389+
}
390+
391+
fn get_f32(v: float32_t) -> f32 {
392+
f32::from_bits(v.to_bits())
393+
}
394+
395+
fn get_f64(v: float64_t) -> f64 {
396+
f64::from_bits(v.to_bits())
397+
}
398+
399+
#[test]
400+
fn test_max_f32_normal_values() {
401+
let mut fpu = FPU::default();
402+
403+
// Test regular positive values
404+
assert_eq!(get_f32(fpu.max(make_f32(1.0), make_f32(2.0))), 2.0);
405+
assert_eq!(get_f32(fpu.max(make_f32(2.0), make_f32(1.0))), 2.0);
406+
407+
// Test regular negative values
408+
assert_eq!(get_f32(fpu.max(make_f32(-1.0), make_f32(-2.0))), -1.0);
409+
assert_eq!(get_f32(fpu.max(make_f32(-2.0), make_f32(-1.0))), -1.0);
410+
411+
// Test mixed sign values
412+
assert_eq!(get_f32(fpu.max(make_f32(-1.0), make_f32(1.0))), 1.0);
413+
assert_eq!(get_f32(fpu.max(make_f32(1.0), make_f32(-1.0))), 1.0);
414+
}
415+
416+
#[test]
417+
fn test_max_f32_special_values() {
418+
let mut fpu = FPU::default();
419+
420+
// Test with infinities
421+
assert_eq!(get_f32(fpu.max(make_f32(f32::INFINITY), make_f32(1.0))), f32::INFINITY);
422+
assert_eq!(get_f32(fpu.max(make_f32(1.0), make_f32(f32::INFINITY))), f32::INFINITY);
423+
assert_eq!(get_f32(fpu.max(make_f32(f32::NEG_INFINITY), make_f32(1.0))), 1.0);
424+
assert_eq!(get_f32(fpu.max(make_f32(1.0), make_f32(f32::NEG_INFINITY))), 1.0);
425+
assert_eq!(get_f32(fpu.max(make_f32(f32::INFINITY), make_f32(f32::NEG_INFINITY))), f32::INFINITY);
426+
427+
// Test with NaN
428+
let nan = f32::NAN;
429+
let result1 = get_f32(fpu.max(make_f32(nan), make_f32(1.0)));
430+
let result2 = get_f32(fpu.max(make_f32(1.0), make_f32(nan)));
431+
432+
assert_eq!(result1, 1.0); // NaN + non-NaN should return non-NaN
433+
assert_eq!(result2, 1.0); // non-NaN + NaN should return non-NaN
434+
435+
// Both NaN should return a canonical NaN
436+
let result_both_nan = fpu.max(make_f32(nan), make_f32(nan));
437+
assert!(get_f32(result_both_nan).is_nan());
438+
439+
// Test with zero
440+
let plus_zero = 0.0f32;
441+
let minus_zero = -0.0f32;
442+
443+
// Max should prefer +0 over -0
444+
let zero_result = get_f32(fpu.max(make_f32(plus_zero), make_f32(minus_zero)));
445+
assert!(zero_result == 0.0 && zero_result.is_sign_positive());
446+
447+
let zero_result = get_f32(fpu.max(make_f32(minus_zero), make_f32(plus_zero)));
448+
assert!(zero_result == 0.0 && zero_result.is_sign_positive());
449+
}
450+
451+
#[test]
452+
fn test_min_f32_normal_values() {
453+
let mut fpu = FPU::default();
454+
455+
// Test regular positive values
456+
assert_eq!(get_f32(fpu.min(make_f32(1.0), make_f32(2.0))), 1.0);
457+
assert_eq!(get_f32(fpu.min(make_f32(2.0), make_f32(1.0))), 1.0);
458+
459+
// Test regular negative values
460+
assert_eq!(get_f32(fpu.min(make_f32(-1.0), make_f32(-2.0))), -2.0);
461+
assert_eq!(get_f32(fpu.min(make_f32(-2.0), make_f32(-1.0))), -2.0);
462+
463+
// Test mixed sign values
464+
assert_eq!(get_f32(fpu.min(make_f32(-1.0), make_f32(1.0))), -1.0);
465+
assert_eq!(get_f32(fpu.min(make_f32(1.0), make_f32(-1.0))), -1.0);
466+
}
467+
468+
#[test]
469+
fn test_min_f32_special_values() {
470+
let mut fpu = FPU::default();
471+
472+
// Test with infinities
473+
assert_eq!(get_f32(fpu.min(make_f32(f32::INFINITY), make_f32(1.0))), 1.0);
474+
assert_eq!(get_f32(fpu.min(make_f32(1.0), make_f32(f32::INFINITY))), 1.0);
475+
assert_eq!(get_f32(fpu.min(make_f32(f32::NEG_INFINITY), make_f32(1.0))), f32::NEG_INFINITY);
476+
assert_eq!(get_f32(fpu.min(make_f32(1.0), make_f32(f32::NEG_INFINITY))), f32::NEG_INFINITY);
477+
assert_eq!(get_f32(fpu.min(make_f32(f32::INFINITY), make_f32(f32::NEG_INFINITY))), f32::NEG_INFINITY);
478+
479+
// Test with NaN
480+
let nan = f32::NAN;
481+
let result1 = get_f32(fpu.min(make_f32(nan), make_f32(1.0)));
482+
let result2 = get_f32(fpu.min(make_f32(1.0), make_f32(nan)));
483+
484+
assert_eq!(result1, 1.0); // NaN + non-NaN should return non-NaN
485+
assert_eq!(result2, 1.0); // non-NaN + NaN should return non-NaN
486+
487+
// Both NaN should return a canonical NaN
488+
let result_both_nan = fpu.min(make_f32(nan), make_f32(nan));
489+
assert!(get_f32(result_both_nan).is_nan());
490+
491+
// Test with zero
492+
let plus_zero = 0.0f32;
493+
let minus_zero = -0.0f32;
494+
495+
// Min should prefer -0 over +0
496+
let zero_result = get_f32(fpu.min(make_f32(plus_zero), make_f32(minus_zero)));
497+
assert!(zero_result == 0.0 && zero_result.is_sign_negative());
498+
499+
let zero_result = get_f32(fpu.min(make_f32(minus_zero), make_f32(plus_zero)));
500+
assert!(zero_result == 0.0 && zero_result.is_sign_negative());
501+
502+
let zero_result = get_f32(fpu.min(make_f32(plus_zero), make_f32(minus_zero)));
503+
assert!(zero_result == 0.0 && zero_result.is_sign_negative());
504+
}
505+
506+
#[test]
507+
fn test_max_f64_normal_values() {
508+
let mut fpu = FPU::default();
509+
510+
// Test regular positive values
511+
assert_eq!(get_f64(fpu.max(make_f64(1.0), make_f64(2.0))), 2.0);
512+
assert_eq!(get_f64(fpu.max(make_f64(2.0), make_f64(1.0))), 2.0);
513+
514+
// Test regular negative values
515+
assert_eq!(get_f64(fpu.max(make_f64(-1.0), make_f64(-2.0))), -1.0);
516+
assert_eq!(get_f64(fpu.max(make_f64(-2.0), make_f64(-1.0))), -1.0);
517+
518+
// Test mixed sign values
519+
assert_eq!(get_f64(fpu.max(make_f64(-1.0), make_f64(1.0))), 1.0);
520+
assert_eq!(get_f64(fpu.max(make_f64(1.0), make_f64(-1.0))), 1.0);
521+
}
522+
523+
#[test]
524+
fn test_min_f64_special_values() {
525+
let mut fpu = FPU::default();
526+
527+
// Test with infinities
528+
assert_eq!(get_f64(fpu.min(make_f64(f64::INFINITY), make_f64(1.0))), 1.0);
529+
assert_eq!(get_f64(fpu.min(make_f64(1.0), make_f64(f64::INFINITY))), 1.0);
530+
assert_eq!(get_f64(fpu.min(make_f64(f64::NEG_INFINITY), make_f64(1.0))), f64::NEG_INFINITY);
531+
assert_eq!(get_f64(fpu.min(make_f64(1.0), make_f64(f64::NEG_INFINITY))), f64::NEG_INFINITY);
532+
533+
// Test with NaN
534+
let nan = f64::NAN;
535+
let result1 = get_f64(fpu.min(make_f64(nan), make_f64(1.0)));
536+
let result2 = get_f64(fpu.min(make_f64(1.0), make_f64(nan)));
537+
538+
assert_eq!(result1, 1.0); // NaN + non-NaN should return non-NaN
539+
assert_eq!(result2, 1.0); // non-NaN + NaN should return non-NaN
540+
541+
// Test with zero
542+
let plus_zero = 0.0f64;
543+
let minus_zero = -0.0f64;
544+
545+
// Min should prefer -0 over +0
546+
let zero_result = get_f64(fpu.min(make_f64(plus_zero), make_f64(minus_zero)));
547+
assert!(zero_result == 0.0 && zero_result.is_sign_negative());
548+
}
549+
}

src/wrapper/common.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ pub trait Float: Sized {
145145
const FRACTION_BIT: Self::Payload;
146146
const SIGN_POS: usize;
147147
const EXPONENT_POS: usize;
148+
const DEFAULT_NAN: Self::Payload;
148149

149150
fn set_payload(&mut self, x: Self::Payload);
150151

src/wrapper/f32.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// SPDX-License-Identifier: MIT OR Apache-2.0
22

3+
use crate::softfloat::defaultNaNF32UI;
4+
35
use super::super::softfloat::{
46
f32_add, f32_div, f32_eq, f32_eq_signaling, f32_isSignalingNaN, f32_le, f32_le_quiet, f32_lt,
57
f32_lt_quiet, f32_mul, f32_mulAdd, f32_rem, f32_roundToInt, f32_sqrt, f32_sub, f32_to_f64,
@@ -30,6 +32,7 @@ impl Float for float32_t {
3032

3133
const EXPONENT_BIT: Self::Payload = 0xff;
3234
const FRACTION_BIT: Self::Payload = 0x7f_ffff;
35+
const DEFAULT_NAN: Self::Payload = defaultNaNF32UI;
3336
const SIGN_POS: usize = 31;
3437
const EXPONENT_POS: usize = 23;
3538

src/wrapper/f64.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// SPDX-License-Identifier: MIT OR Apache-2.0
22

3+
use crate::softfloat::defaultNaNF64UI;
4+
35
use super::super::softfloat::{
46
f64_add, f64_div, f64_eq, f64_eq_signaling, f64_isSignalingNaN, f64_le, f64_le_quiet, f64_lt,
57
f64_lt_quiet, f64_mul, f64_mulAdd, f64_rem, f64_roundToInt, f64_sqrt, f64_sub, f64_to_f32,
@@ -30,6 +32,7 @@ impl Float for float64_t {
3032

3133
const EXPONENT_BIT: Self::Payload = 0x7ff;
3234
const FRACTION_BIT: Self::Payload = 0xf_ffff_ffff_ffff;
35+
const DEFAULT_NAN: Self::Payload = defaultNaNF64UI;
3336
const SIGN_POS: usize = 63;
3437
const EXPONENT_POS: usize = 52;
3538

0 commit comments

Comments
 (0)