Skip to content
Draft
7 changes: 7 additions & 0 deletions posit/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright ©2021 The Gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package posit implements a generalized unum type II implementation
// as a byte slice and provides primitives for working with posits.
package posit
143 changes: 143 additions & 0 deletions posit/posit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Copyright ©2021 The Gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package posit

import (
"fmt"
"math"
)

type Posit []byte

func (p Posit) regime() (k int) {
// Regime lead bit.
const rbit = 0b0100_0000
// Indicates if leading Regime bit is a one.
oneRun := rbit&p[0] != 0
outer:
for i := range p {
for j := 0; j < 8; j++ {
// Ignore sign bit.
if i == 0 && j == 0 {
j++
}
// If current bit is part of the run, add to r.
if (oneRun && p[i]&(1<<(7-j)) != 0) || (!oneRun && p[i]&(1<<(7-j)) == 0) {
k++
} else {
break outer
}
}
}
if !oneRun {
k *= -1
} else {
k--
}
return k
}

// sign returns true if posit negative
func (p Posit) sign() bool { return p[0]&(1<<7) != 0 }

func (p Posit) bits() int { return 8 * len(p) }

func (p Posit) String() string { return fmt.Sprintf("%08b", []byte(p)) }

// es represents max amount of exponent bits that can be present in the posit.
//
// Values taken from http://www.johngustafson.net/pdfs/BeatingFloatingPoint.pdf
// (table 3) to match or exceed IEEE float dynamic range.
func (p Posit) es() (es int) {
switch p.bits() {
case 16:
es = 1
case 32:
es = 3
case 64:
es = 4
case 128:
es = 7
case 256:
es = 10
default:
es = p.bits() / 16 // 8 bit posit has es == 0.
}
return es
}

// exp returns the exponent part of the posit (2**e).
func (p Posit) exp() (exp int) {
// bits in front of exp.
flen := p.regimeLen() + 2 // sign and opposite bits included
es := p.es()
// Check if exp bits present for quick return.
if flen >= p.bits() || es == 0 {
return 0
}

expcount := 0
outer:
for i := flen / 8; i < len(p); i++ {
for j := flen % 8; j < 8; j++ {
if expcount == es {
break outer
}
exp <<= 1
exp |= (int(p[i]) & (1 << (7 - j))) >> (7 - j)
expcount++
}
}
return exp
}

// returns regime length for a given posit in number of bits.
func (p Posit) regimeLen() int {
r := p.regime()
if r < 0 {
return -r
}
return r + 1
}

// useed defines the midway point of accuracy from 1 to +inf and
// conversely from 1 to 0. It depends on es.
func (p Posit) useed() int { return 1 << (1 << (p.es())) }

// fraction returns the numerator of the fraction part.
func (p Posit) fraction() (frac int) {
// bits in front of fraction.
flen := p.regimeLen() + p.es() + 2 // sign and opposite bits included
// Check if exp bits present for quick return.
if flen >= p.bits() {
return 0
}

for i := flen / 8; i < len(p); i++ {
for j := flen % 8; j < 8; j++ {
frac <<= 1
frac |= (int(p[i]) & (1 << (7 - j))) >> (7 - j)
}
}
return frac
}

func (p Posit) ToFloat64() float64 {
reg := float64(p.regime())
useed := float64(p.useed())
exp := 1 << p.exp()
return math.Pow(useed, reg) * float64(exp) * (1 + float64(p.fraction())/useed)
}

// Format implements fmt.Formatter.
func (p Posit) Format(fs fmt.State, c rune) {
switch c {
case 'v', 'f':
fmt.Fprintf(fs, "%T{%f}", p, p.ToFloat64())
default:
fmt.Fprintf(fs, "%%!%c(%T=%[2]v)", c, p)
return
}
}
121 changes: 121 additions & 0 deletions posit/posit_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Copyright ©2021 The Gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package posit

import (
"math"
"testing"
)

func TestRegime(t *testing.T) {
for _, test := range []struct {
p Posit
expect int
}{
// posit8
{Posit{0b_100_0000}, 0},
{Posit{0b_111_0000}, 2},
{Posit{0b_010_0000}, -1},
{Posit{0b_111_1000}, 3},
{Posit{0b_000_0100}, -4},

// posit16
{Posit{0, 0b0010_0000}, -9},
{Posit{0xff, 0b0010_0000}, 6},

// posit 32
{Posit{0, 0, 0b1000_0001, 0}, -7 - 8},
{Posit{0, 0, 0xff, 0}, -7 - 8},
{Posit{0xff, 0xff, 0xff, 0}, 7 + 8 + 8 - 1},
{Posit{0xff, 0, 0xff, 0}, 7 - 1},
} {
got := test.p.regime()
if got != test.expect {
t.Errorf("posit %s expected regime %v, got %v", test.p, test.expect, got)
}
}
}

func TestSign(t *testing.T) {
for _, test := range []struct {
p Posit
expect bool
}{
{Posit{0b_1100_0000}, true},
{Posit{0b_0111_0000}, false},
{Posit{0b1010_0000}, true},
{Posit{0b_0111_1000}, false},
{Posit{0b_1000_0100}, true},
{Posit{0, 0b0010_0000}, false},
} {
got := test.p.sign()
if got != test.expect {
t.Errorf("posit %s expected sign %t, got %t", test.p, test.expect, got)
}
}
}

func TestUseed(t *testing.T) {
for _, test := range []struct {
p Posit
}{
{Posit{0b1100_0000}},

{Posit{0, 0b0010_0000}},
{Posit{0, 0b0010_0000, 0}},
} {
es := test.p.es()
expect := int(math.RoundToEven(math.Pow(2, math.Pow(2, float64(es)))))
got := test.p.useed()
if expect != got {
t.Errorf("posit %s expected useed %v, got %v", test.p, expect, got)
}
}
}

func TestExp(t *testing.T) {
for _, test := range []struct {
p Posit
expect int
}{
// posit16 has es == 1
{Posit{0b_101_0000, 0}, 1},
{Posit{0b_100_0000, 0}, 0},
{Posit{0b_111_1001, 0}, 0},
{Posit{0b_111_1011, 0}, 1},
// posit 32 has es == 3
{Posit{0, 0, 0b10111, 0}, 3},
{Posit{0, 0, 0b11111, 0}, 7},
} {
got := test.p.exp()
expect := test.expect

if expect != got {
t.Errorf("posit %s expected exp %v, got %v", test.p, expect, got)
}
}
}
func TestFraction(t *testing.T) {
for _, test := range []struct {
p Posit
expect int
}{
// posit16 has es == 1
{Posit{0b_101_0000, 0}, 1},
{Posit{0b_100_0000, 0}, 0},
{Posit{0b_111_1001, 0}, 0},
{Posit{0b_111_1011, 0}, 1},
// posit 32 has es == 3
{Posit{0, 0, 0b10111, 0}, 3},
{Posit{0, 0, 0b11111, 0}, 7},
} {
got := test.p.fraction()
expect := test.expect

if expect != got {
t.Errorf("posit %s expected exp %v, got %v", test.p, expect, got)
}
}
}