Skip to content

Commit

Permalink
Merge pull request #4 from bksaiki/posit
Browse files Browse the repository at this point in the history
Posit support
  • Loading branch information
bksaiki authored Oct 13, 2023
2 parents e40a7ab + 8740196 commit 832d1a2
Show file tree
Hide file tree
Showing 9 changed files with 883 additions and 16 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
Rust library for simulating number systems.

Supports IEEE-754 floating-point numbers, fixed-point numbers, and more.
Inspired by [FPCore](https://fpbench.org/) and
Bill Zorn's [Titanic](https://github.com/billzorn/titanic) library.
Inspired by [FPCore](https://fpbench.org/) and the [Titanic](https://github.com/billzorn/titanic) library.

This library is hosted as a crate [here](https://crates.io/crates/mpmfnum).
The documentation can be found [here](https://docs.rs/mpmfnum/latest/mpmfnum/).
Expand Down
2 changes: 1 addition & 1 deletion src/ieee754/number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ impl IEEE754 {
}

/// Converts this [`IEEE754`] to an [`Integer`] representing an IEEE 754 bitpattern.
pub fn into_bits(&self) -> Integer {
pub fn into_bits(self) -> Integer {
let nbits = self.ctx.nbits();
let (s, unsigned) = match &self.num {
IEEE754Val::Zero(s) => (*s, Integer::zero()),
Expand Down
18 changes: 8 additions & 10 deletions src/ieee754/round.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::ops::{BitAnd, BitOr};

use rug::Integer;
use std::ops::{BitAnd, BitOr};

use crate::ieee754::{Exceptions, IEEE754Val, IEEE754};
use crate::rfloat::{RFloat, RFloatContext};
Expand Down Expand Up @@ -61,11 +60,12 @@ impl IEEE754Context {
es
);
assert!(
nbits >= es + 3,
nbits >= es + Self::PREC_MIN,
"total bitwidth needs to be at least {} bits, given {} bits",
es + 3,
es + Self::PREC_MIN,
nbits
);

Self {
es,
nbits,
Expand Down Expand Up @@ -97,9 +97,7 @@ impl IEEE754Context {

/// Returns the exponent bitwidth of the format produced by
/// this context (when viewed as a bitvector). This is guaranteed
/// to satisfy `2 <= self.es() <= self.nbits() - 2. Exponent
/// overflowing will likely occur past 60 bits, but MPFR generally
/// has a limit at 31 bits.
/// to satisfy `2 <= self.es() < self.nbits() - 2.
pub fn es(&self) -> usize {
self.es
}
Expand All @@ -121,7 +119,7 @@ impl IEEE754Context {

/// Returns the total bitwidth of the format produced by this context
/// (when viewed as a bitvector). This is guaranteed to satisfy
/// `self.es() + 2 <= self.nbits()`.
/// `self.es() + 2 < self.nbits()`.
pub fn nbits(&self) -> usize {
self.nbits
}
Expand Down Expand Up @@ -240,7 +238,7 @@ impl IEEE754Context {
let m = b.bitand(bitmask(p - 1));

// case split by classification
let e_norm = e - self.emax();
let e_norm = e.to_isize().unwrap() - self.emax();
let num = if e_norm < self.emin() {
// subnormal or zero
if m.is_zero() {
Expand All @@ -253,7 +251,7 @@ impl IEEE754Context {
} else if e_norm <= self.emax() {
// normal
let c = (Integer::from(1) << (p - 1)).bitor(m);
let exp = e_norm.to_isize().unwrap() - (p as isize - 1);
let exp = e_norm - (p as isize - 1);
IEEE754Val::Normal(s, exp, c)
} else {
// non-real
Expand Down
8 changes: 5 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,18 @@
//! rounds a [`Real`] value to a floating-point number as described by
//! the IEEE 754 standard,
//! - [`FixedContext`][crate::fixed::FixedContext]
//! rounds a [`Real`] value to a fixed-point numbers.
//!
//! Planned support for posits and more!
//! rounds a [`Real`] value to a fixed-point numbers,
//! - [`PositContext`][crate::posit::PositContext]
//! rounds a [`Real`] value to a posit number as described
//! by the Posit standard.
//!

pub mod fixed;
pub mod float;
pub mod ieee754;
pub mod math;
pub mod ops;
pub mod posit;
pub mod real;
pub mod rfloat;

Expand Down
14 changes: 14 additions & 0 deletions src/posit/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//! "Posit" numbers as described in the 2022 Posit Standard.
//!
//! This module implements posits with [`PositContext`].
//! The associated storage type is [`Posit`] which represents
//! a posit number.

mod number;
mod ops;
mod round;

pub use number::Posit;
pub(crate) use number::PositVal;
pub use ops::*;
pub use round::PositContext;
226 changes: 226 additions & 0 deletions src/posit/number.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
use std::cmp::Ordering;

use num_traits::{One, Zero};
use rug::Integer;

use crate::{rfloat::RFloat, util::bitmask, Real};

use super::PositContext;

/// Posit number encoding viewed as an enumeration.
/// Unlike [`Posit`], [`PositVal`] represents only numerical data.
#[derive(Clone, Debug)]
pub enum PositVal {
/// Exact zero
Zero,
/// Finite, non-zero value
NonZero(bool, isize, isize, Integer),
/// Non-real or undefined
Nar,
}

/// Posit number format.
///
/// The associated [`RoundingContext`][crate::RoundingContext]
/// implementation is [`PositContext`].
/// See [`PositContext`] for more details on numerical properties
/// of the [`Posit`] type.
#[derive(Clone, Debug)]
pub struct Posit {
pub(crate) num: PositVal,
pub(crate) ctx: PositContext,
}

impl Posit {
/// Returns the rounding context under which this number was created.
pub fn ctx(&self) -> &PositContext {
&self.ctx
}

/// Converts this [`Posit`] to an [`Integer`] representing a posit bitpattern.
pub fn into_bits(self) -> Integer {
let es = self.ctx.es();
let nbits = self.ctx.nbits();
match self.num {
PositVal::Zero => Integer::from(0),
PositVal::Nar => Integer::from(1) << (nbits - 1),
PositVal::NonZero(s, r, exp, c) => {
// convert sign
let sfield = if s { Integer::one() } else { Integer::zero() };

// compute size of regime field and regime LSB
let (kbits, r0) = if r < 0 {
(-r as usize, false)
} else {
(r as usize + 1, true)
};

// check for special case: format encoded with sign + regime
if kbits == nbits - 1 {
sfield << (nbits - 1) | bitmask(nbits - 1)
} else {
// compute size of exponent and significand fields
let rbits = kbits + 1;
let embits = nbits - (rbits + 1);
let (ebits, mbits) = if embits <= es {
(embits, 0)
} else {
(es, embits - es)
};

// convert regime
let rfield = if r0 {
// !r0 => rfield = 11..110
bitmask(kbits) << 1
} else {
// r0 => rfield = 00..001
Integer::one()
};

// convert exponent
let e = exp + (c.significant_bits() as isize - 1);
let efield = Integer::from(e >> (es - ebits));

// convert significand
let p = c.significant_bits() as usize;
let mfield = bitmask(p - 1) & c;

// compose
(sfield << (nbits - 1)) | (rfield << embits) | (efield << mbits) | mfield
}
}
}
}
}

impl Real for Posit {
fn radix() -> usize {
2
}

fn sign(&self) -> bool {
self.is_negative().unwrap_or(false)
}

fn exp(&self) -> Option<isize> {
match &self.num {
PositVal::Zero => None,
PositVal::NonZero(_, r, exp, _) => Some((r * self.ctx.useed()) + exp),
PositVal::Nar => None,
}
}

fn e(&self) -> Option<isize> {
match &self.num {
PositVal::Zero => None,
PositVal::NonZero(_, r, exp, c) => {
Some(((r * self.ctx.useed()) + exp - 1) + (c.significant_bits() as isize))
}
PositVal::Nar => None,
}
}

fn n(&self) -> Option<isize> {
match &self.num {
PositVal::Zero => None,
PositVal::NonZero(_, r, exp, _) => Some((r * self.ctx.useed()) + exp - 1),
PositVal::Nar => None,
}
}

fn c(&self) -> Option<Integer> {
match &self.num {
PositVal::Zero => None,
PositVal::NonZero(_, _, _, c) => Some(c.clone()),
PositVal::Nar => None,
}
}

fn m(&self) -> Option<Integer> {
self.c().map(|c| if self.sign() { -c } else { c })
}

fn p(&self) -> usize {
match &self.num {
PositVal::Zero => 0,
PositVal::NonZero(_, _, _, c) => c.significant_bits() as usize,
PositVal::Nar => 0,
}
}

fn is_nar(&self) -> bool {
matches!(self.num, PositVal::Nar)
}

fn is_finite(&self) -> bool {
!matches!(self.num, PositVal::Nar)
}

fn is_infinite(&self) -> bool {
false
}

fn is_zero(&self) -> bool {
matches!(self.num, PositVal::Zero)
}

fn is_negative(&self) -> Option<bool> {
match &self.num {
PositVal::Zero => None,
PositVal::NonZero(s, _, _, _) => Some(*s),
PositVal::Nar => None,
}
}

fn is_numerical(&self) -> bool {
!matches!(self.num, PositVal::Nar)
}
}

impl PartialEq for Posit {
fn eq(&self, other: &Self) -> bool {
self.partial_cmp(other) == Some(Ordering::Equal)
}
}

impl PartialOrd for Posit {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (&self.num, &other.num) {
(PositVal::Nar, PositVal::Nar) => Some(Ordering::Equal),
(PositVal::Nar, _) => Some(Ordering::Less),
(_, PositVal::Nar) => Some(Ordering::Greater),
(PositVal::Zero, PositVal::Zero) => Some(Ordering::Equal),
(PositVal::Zero, PositVal::NonZero(s, _, _, _)) => {
if *s {
// 0 > -x
Some(Ordering::Greater)
} else {
// 0 < +X
Some(Ordering::Less)
}
}
(PositVal::NonZero(s, _, _, _), PositVal::Zero) => {
if *s {
// -x < 0
Some(Ordering::Less)
} else {
// +x > 0
Some(Ordering::Greater)
}
}
(PositVal::NonZero(_, _, _, _), PositVal::NonZero(_, _, _, _)) => {
RFloat::from(self.clone()).partial_cmp(&RFloat::from(other.clone()))
}
}
}
}

impl From<Posit> for RFloat {
fn from(value: Posit) -> Self {
match value.num {
PositVal::Zero => RFloat::zero(),
PositVal::NonZero(s, r, exp, c) => RFloat::Real(s, value.ctx.rscale() * r + exp, c),
PositVal::Nar => RFloat::Nan,
}
}
}
Loading

0 comments on commit 832d1a2

Please sign in to comment.