SoftPosit (C/C++) : https://gitlab.com/cerlane/SoftPosit
SoftPosit-Python : https://gitlab.com/cerlane/SoftPosit-Python
SoftFloat-Python: https://gitlab.com/cerlane/SoftFloat-Python
More information: https://posithub.org
Questions: contact@posithub.org
Prepared by: LEONG Siew Hoon (Cerlane)
Date: 31 Aug 2018
pip install softposit --user
import softposit as sp
In order to faciliate the comparison between Posits and Floats, please also install SoftFloat-Python.
Installation
pip install softfloat --user
import softfloat as sf
The 3 recommended posit types are
Type | Posit Size (ps) | Exponent Size (es) | Precisions |
posit8 | 8 bits | 0 bits | ~3 decimal digits |
posit16 | 16 bits | 1 bits | ~5 decimal digits |
posit32 | 32 bits | 2 bits | ~10 decimal digits |
There are two ways to set the values of a posit type.
(1) Assign decimal values in integers or floats to a posit variable. In event that there is no exact value to represent that value for the particular ps and es, the value will be rounded.
(2) Set the actual bits of the posit using decimal values in integers.
Note: Posits have only one rounding mode, i.e. round to nearest even bit (Banker's rounding).
#Different Standard Posit Types
p8=sp.posit8(0.3)
print("Posit8:", p8)
p8.toBinaryFormatted()
p16=sp.posit16(0.3)
print("\nPosit16:", p16)
p16.toBinaryFormatted()
p32=sp.posit32(0.3)
print("\nPosit32:", p32)
p32.toBinaryFormatted()
#Assign values by bits
print('Using Integers:')
p8.fromBits(6)
p8.toBinaryFormatted()
print('\nUsing Hex:')
p8.fromBits(6)
p8.toBinaryFormatted()
print('\nUsing Binary String:')
p8.fromBits(int("10110011", 2))
p8.toBinaryFormatted()
# Checking Posit types
print("p8 type:", p8.type(), "\n")
print("p16 type:", p16.type(), "\n")
print("p32 type:", p32.type(), "\n")
Bit Type | Posit | Float |
Sign bit | Yes (1-bit) | Yes (1-bit) |
Regime bits | Yes (1 to (ps-1) bits |
No |
Exponent bits | Yes/No Depending on posit type and regime length Predeterminded (fixed) length |
Yes Depending on float (16/32/64) type Predetermined (fixed) length |
Fraction bits | Yes/No Depending on regime length Dynamic length |
Yes Depending on float (16/32/64) type Predetermined (fixed) length |
Negating a posit is similar to negating an integer value, i.e. Performing a Two's complement. This is different from IEEE 754 Float where negating a float involves inverting only the sign bit.
The nearest value to ± 0.3 for Posit16:
#Sign bit: positive and negative posits
p16=sp.posit16(0.3)
print("For a value of", p16, ":")
p16.toBinaryFormatted()
print("\nFor a value of", -p16, ":")
(-p16).toBinaryFormatted()
The nearest value to ± 0.3 for Float16:
#Sign bit: positive and negative posits
f16=sf.float16(0.3)
print("For a value of", f16, ":")
f16.toBinaryFormatted()
print("\nFor a value of", -f16, ":")
(-f16).toBinaryFormatted()
Regime bits is a continuous sequence of the same sign bit and is terminated by a different sign bit.
Regime length is dynamic and can be as long as the length of ps (i.e. ps-1).
Regime is the main reason why posits can have a bigger dynamic range than floats.
Note: Regime bits are a purely posit type of bits.
#Regime bits
print("posit16: 0.00390625")
p16 = sp.posit16(0.00390625);
p16.toBinaryFormatted()
print("Four 0-bit terminated by a 1-bit. This implies regime runlength is 4.\n")
print("posit16: -0.00390625")
p16 = sp.posit16(-0.00390625);
p16.toBinaryFormatted()
print("Five 1-bit terminated by a 0-bit. This implies regime runlength is 5.\n")
print("posit16: 268435456")
p16 = sp.posit16(268435456) #maxpos
p16.toBinaryFormatted()
print("Fifteen 1-bit without a terminating bit, exponent bit and fraction bits. Regime runlength is 15 (ps-1).")
Exponent has a fixed size/length that is determined when the posit type is chosen.
In the case of posit8, posit16 and posit32, es is 0, 1 and 2 respectively.
As regime size is dynamic, the exponent bit(s) can be pushed out of the range of ps.
print("posit16: 4.76837158203125e-7")
p16 = sp.posit16(4.76837158203125e-7)
p16.toBinaryFormatted()
print("\nShift bit to the right 3 times:");
p16>>=3
p16.toBinaryFormatted()
print("Exponent bit is being pushed out.");
print("posit16: ", p16)
p32.fromBits(0x7)
print("\nposit32: ", p32)
p32.toBinaryFormatted()
print("\nShift bit to the right 1 time:");
p32>>=1
p32.toBinaryFormatted()
print("One exponent bit is being pushed out.");
print("posit32: ", p32)
The length of fraction is determined by regime and exponent.
The minimum length is 0 and the maximum length for posit8, posit16 and posit32 is 5, 12 and 27 bits (excluding the hidden bit).
As regime size is dynamic, the fraction bits can be pushed out of the range of ps.
p8.fromBits(0x3F)
print("Posit8:", p8)
p8.toBinaryFormatted()
p8.fromBits(0x1)
print("\nPosit8:", p8)
p8.toBinaryFormatted()
print("Float: Positive zero:")
f16 = sf.float16(+0.0)
f16.toBinaryFormatted()
print("\nFloat: Negative zero:")
f16 = sf.float16(-0.0)
f16.toBinaryFormatted()
print("\nPosit: Positive zero:")
p16 = sp.posit16(+0.0)
p16.toBinaryFormatted()
print("\nPosit: Negative zero:")
p16 = sp.posit16(-0.0)
p16.toBinaryFormatted()
Posit has only one bit pattern to represents infinity, undefined and unrepresented values.
$NaR$ -> 10000000 00000000
IEEE 754 uses $NaN$ (Not-a-Number) to represent undefined and unrepresentable values.
Infinity
#Posit
print("Posit: NaR")
p16.toNaR()
p16.toBinaryFormatted()
print("\nPosit: Divide by zero")
p16=sp.posit16(1)
p16/=0
p16.toBinaryFormatted()
print(p16)
print("\nPosit: Square root(negative number)")
p16=sp.posit16(-1)
p16.sqrt()
p16.toBinaryFormatted()
print(p16)
#Float
print("\nFloat: NaN - Divide by zero")
f16 = sf.float16(1)
f16/=0
f16.toBinaryFormatted()
print(f16)
print("\nFloat: NaN - Divide by zero")
f16 = sf.float16(-1)
f16/=0
f16.toBinaryFormatted()
print(f16)
print("\nFloat: Square root(negative number)")
f16 = sf.float16(-1)
f16.sqrt()
f16.toBinaryFormatted()
print(f16)
#try a different bit pattern for NaN
print("\n")
f16.fromBits(0xFE02)
f16.toBinaryFormatted()
print(f16)
Although computers store all numbers as binary (base 2), most of us use decimal (base 10) to visual the value of a number.
To convert a posit from base 2 to base 10, the following has to be done:
$p = 0$, if $\textit{bit}_0..\textit{bit}_{n-1} = 0$,
$p = \textit{NaR}$, if $\textit{bit}_0=1, \textit{bit}_1..\textit{bit}_{n-1} = 0$,
$p = (-1)^{\textit{sign}} \times \textit{useed}^k \times 2^e \times (1 + \frac{f}{2^\textit{fs}} )$, otherwise (note that posit should be in 2's complement form if sign=1)
where
$p$ is the posit value represented,
$n$ is the posit size (total number of bits in the posit),
$\textit{NaR}$ is Not-a-Real,
$\textit{sign}$ is $\textit{bit}_{0}$,
$\textit{useed}$ is ${2^2}^\textit{es}$ and $\textit{es}$ is the exponent size,
$e$ is the exponent value in the $\textit{es}$ bits,
$k$ is the scaling factor expressed by the regime (refer to pseudo code),
$f$ is the fraction value and
$\textit{fs}$ is the fraction size in bits.
Calculate k
if (first regime bit == 0)
k = - runlength
else if (first regime bit == 1)
k = runlength -1
Using the same method as example one, please compute the value of the following posit32 (es=2) binary:
00000001 01010111 00001111 10111000
#Example One
p16 = sp.posit16(0.3)
p16.toBinaryFormatted()
print("Actual p16 value: ", p16)
es = 1
sign=0
useed=pow(2, pow(2,es))
e=0
k=-1
f=int("001100110011", 2)
p = pow((-1), sign) * pow(useed, k) * pow(2, e) * (1+ (f/(int("111111111111",2))))
print("Rounded p value:", p)
print("P value to 14 decimal points: {0:.14f}".format(round(p,2)))
print("P value to 14 decimal points: {0:.20f}".format(round(p,2)))
#Exercise - Please write your code below: