type ExceptionType = cty::uint32_t;
#[derive(Debug)]
#[repr(C)]
pub enum RoundingMode {
ToNearest = 0,
TowardZero = 1,
Upward = 2,
Downward = 3,
}
#[derive(Debug)]
#[repr(C)]
pub enum ExceptionFlags {
None = 0,
DivByZero = 1 << 0,
Invalid = 1 << 1,
Overflow = 1 << 2,
Underflow = 1 << 3,
Inexact = 1 << 4,
}
impl Into<ExceptionType> for ExceptionFlags {
fn into(self) -> ExceptionType {
self as ExceptionType
}
}
impl std::ops::BitAnd<ExceptionFlags> for ExceptionType {
type Output = ExceptionType;
fn bitand(self, rhs: ExceptionFlags) -> ExceptionType {
self & (rhs as ExceptionType)
}
}
pub struct Exception(ExceptionType);
impl Exception {
pub fn is_none(&self) -> bool {
self.0 == ExceptionFlags::None.into()
}
pub fn has(&self, exception: ExceptionFlags) -> bool {
(self.0 & exception) != 0
}
pub fn only(&self, exception: ExceptionFlags) -> bool {
self.0 == exception.into()
}
}
#[repr(C)]
struct Result32 {
value: f32,
exception: ExceptionType,
}
impl Result32 {
fn new() -> Self {
Self {
value: 0.0,
exception: 0,
}
}
}
mod ffi {
use super::Result32;
macro_rules! export_ff_binary {
($name:tt) => {
pub fn $name(
a: cty::c_float,
b: cty::c_float,
rm: cty::c_uint,
out: *mut Result32,
) -> cty::c_void;
};
}
macro_rules! export_ff_unary {
($name:tt, $in_ty:ty) => {
pub fn $name(val: $in_ty, rm: cty::c_uint, out: *mut Result32) -> cty::c_void;
};
}
extern "C" {
export_ff_binary!(add_f32);
export_ff_binary!(div_f32);
export_ff_unary!(cvt_u32_f32, cty::c_uint);
}
}
macro_rules! impl_binary {
($name:tt) => {
pub fn $name(a: f32, b: f32, rm: RoundingMode) -> (f32, Exception) {
let mut result = Result32::new();
unsafe { ffi::$name(a, b, rm as cty::c_uint, &mut result) };
(result.value, Exception(result.exception))
}
};
}
macro_rules! impl_unary {
($name:tt, $ty:ty) => {
pub fn $name(val: $ty, rm: RoundingMode) -> (f32, Exception) {
let mut result = Result32::new();
unsafe { ffi::$name(val, rm as cty::c_uint, &mut result) };
(result.value, Exception(result.exception))
}
};
}
impl_binary!(add_f32);
impl_binary!(div_f32);
impl_unary!(cvt_u32_f32, u32);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add_f32() {
let (r_1, exc_1) = add_f32(1.0, 2.0, RoundingMode::ToNearest);
assert_eq!(r_1, 3.0f32);
assert!(exc_1.is_none());
}
#[test]
fn test_add_f32_rounding() {
assert_eq!(add_f32(1.0, 1e-10, RoundingMode::ToNearest).0, 1.0);
assert_eq!(add_f32(1.0, 1e-10, RoundingMode::TowardZero).0, 1.0);
assert_eq!(add_f32(1.0, 1e-10, RoundingMode::Upward).0, 1.0000001);
assert_eq!(add_f32(1.0, 1e-10, RoundingMode::Downward).0, 1.0);
}
#[test]
fn test_add_f32_fpe() {
let (r_1, exc_1) = add_f32(1.0, 1e-10, RoundingMode::ToNearest);
assert_eq!(r_1, 1.0);
assert!(exc_1.only(ExceptionFlags::Inexact));
let (_, exc_1) = add_f32(f32::MAX, f32::MAX, RoundingMode::ToNearest);
assert!(exc_1.has(ExceptionFlags::Overflow));
}
#[test]
fn test_div_f32() {
let (r_1, exc_1) = div_f32(10.0, 2.0, RoundingMode::ToNearest);
assert_eq!(r_1, 5.0f32);
assert!(exc_1.is_none());
}
#[test]
fn test_div_f32_rounding() {
assert_eq!(div_f32(1.0, 2.1, RoundingMode::ToNearest).0, 0.4761905);
assert_eq!(div_f32(1.0, 2.1, RoundingMode::TowardZero).0, 0.47619048);
assert_eq!(div_f32(1.0, 2.1, RoundingMode::Upward).0, 0.4761905);
assert_eq!(div_f32(1.0, 2.1, RoundingMode::Downward).0, 0.47619048);
}
#[test]
fn test_div_f32_fpe() {
let (r_1, exc_1) = div_f32(1.0, 2.1, RoundingMode::ToNearest);
assert_eq!(r_1, 0.4761905);
assert!(exc_1.only(ExceptionFlags::Inexact));
let (r_1, exc_1) = div_f32(1.0, 0.0, RoundingMode::ToNearest);
assert_eq!(r_1.is_infinite(), true);
assert!(exc_1.only(ExceptionFlags::DivByZero));
}
#[test]
fn test_cvt_u32_f32() {
let (r_1, exc_1) = cvt_u32_f32(10, RoundingMode::ToNearest);
assert_eq!(r_1, 10.0f32);
assert!(exc_1.is_none());
}
#[test]
fn test_cvt_u32_f32_rounding() {
assert_eq!(cvt_u32_f32(10, RoundingMode::ToNearest).0, 10.0);
assert_eq!(cvt_u32_f32(10, RoundingMode::TowardZero).0, 10.0);
assert_eq!(cvt_u32_f32(10, RoundingMode::Upward).0, 10.0);
assert_eq!(cvt_u32_f32(10, RoundingMode::Downward).0, 10.0);
}
}