les: use fixed64

This commit is contained in:
rjl493456442 2020-03-02 17:35:21 +08:00
parent 6714ac0933
commit 3d99858372
7 changed files with 100 additions and 62 deletions

View file

@ -35,8 +35,8 @@ const (
// expirationController controls the exponential expiration of positive and negative
// balances
type expirationController interface {
posExpiration(mclock.AbsTime) float64
negExpiration(mclock.AbsTime) float64
posExpiration(mclock.AbsTime) fixed64
negExpiration(mclock.AbsTime) fixed64
}
// priceFactors determine the pricing policy (may apply either to positive or

View file

@ -25,11 +25,11 @@ import (
type zeroExpCtrl struct{}
func (z zeroExpCtrl) posExpiration(mclock.AbsTime) float64 {
func (z zeroExpCtrl) posExpiration(mclock.AbsTime) fixed64 {
return 0
}
func (z zeroExpCtrl) negExpiration(mclock.AbsTime) float64 {
func (z zeroExpCtrl) negExpiration(mclock.AbsTime) fixed64 {
return 0
}

View file

@ -20,7 +20,6 @@ import (
"bytes"
"encoding/binary"
"io"
"math"
"time"
"github.com/ethereum/go-ethereum/common"
@ -138,18 +137,18 @@ func (db *nodeDB) key(id []byte, neg bool) []byte {
return append(prefix, id...)
}
func (db *nodeDB) getExpiration() (float64, float64) {
func (db *nodeDB) getExpiration() (fixed64, fixed64) {
blob, err := db.db.Get(expirationKey)
if err != nil || len(blob) != 16 {
return 0, 0
}
return math.Float64frombits(binary.BigEndian.Uint64(blob[:8])), math.Float64frombits(binary.BigEndian.Uint64(blob[8:16]))
return fixed64(binary.BigEndian.Uint64(blob[:8])), fixed64(binary.BigEndian.Uint64(blob[8:16]))
}
func (db *nodeDB) setExpiration(pos, neg float64) {
func (db *nodeDB) setExpiration(pos, neg fixed64) {
var buff [16]byte
binary.BigEndian.PutUint64(buff[:8], math.Float64bits(pos))
binary.BigEndian.PutUint64(buff[8:16], math.Float64bits(neg))
binary.BigEndian.PutUint64(buff[:8], uint64(pos))
binary.BigEndian.PutUint64(buff[8:16], uint64(neg))
db.db.Put(expirationKey, buff[:16])
}

View file

@ -67,7 +67,7 @@ func TestNodeDB(t *testing.T) {
}
}
}
posExp, negExp := 100.1234, 200.5678
posExp, negExp := fixed64(1000), fixed64(2000)
ndb.setExpiration(posExp, negExp)
if pos, neg := ndb.getExpiration(); pos != posExp || neg != negExp {
t.Fatalf("Expiration mismatch, want %v / %v, got %v / %v", posExp, negExp, pos, neg)

View file

@ -95,10 +95,11 @@ type clientPool struct {
disableBias bool // Disable connection bias(used in testing)
// fields in this group are protected by expLock
expLock sync.RWMutex
posExpTC, negExpTC uint64
posExp, negExp float64
freeRatioLastUpdate mclock.AbsTime
expLock sync.RWMutex
posExpTC, negExpTC uint64
posExp, negExp fixed64
posExpTCi, negExpTCi float64 // already inverted (logMultiplier/time)
freeRatioLastUpdate mclock.AbsTime
}
// clientPoolPeer represents a client peer in the pool.
@ -201,7 +202,7 @@ func newClientPool(db ethdb.Database, minCap, freeClientCap uint64, clock mclock
// and both of these decay exponentially over time. Delete them if the
// value is small enough.
ndb.evictCallBack = func(now mclock.AbsTime, neg bool, b tokenBalance) bool {
var expiration float64
var expiration fixed64
if neg {
expiration = pool.negExpiration(now)
} else {
@ -292,13 +293,8 @@ func (f *clientPool) updateFreeRatio() {
f.averageFreeRatio -= (f.freeRatio - f.averageFreeRatio) * math.Expm1(-float64(dt)/float64(freeRatioTC))
f.freeRatioLastUpdate = now
dt /= mclock.AbsTime(time.Second)
if f.posExpTC != 0 {
f.posExp += float64(dt) / float64(f.posExpTC) * f.freeRatio
}
if f.negExpTC != 0 {
f.negExp += float64(dt) / float64(f.negExpTC) * f.freeRatio
}
f.posExp += fixed64(float64(dt) * f.posExpTCi * f.freeRatio)
f.negExp += fixed64(float64(dt) * f.negExpTCi * f.freeRatio)
f.expLock.Unlock()
}
@ -311,6 +307,16 @@ func (f *clientPool) setExpirationTCs(pos, neg uint64) {
f.expLock.Lock()
f.posExpTC, f.negExpTC = pos, neg
if pos > 0 {
f.posExpTCi = fixedFactor / float64(pos*uint64(time.Second))
} else {
f.posExpTCi = 0
}
if neg > 0 {
f.negExpTCi = fixedFactor / float64(neg*uint64(time.Second))
} else {
f.negExpTCi = 0
}
f.expLock.Unlock()
}
@ -325,7 +331,7 @@ func (f *clientPool) getExpirationTCs() (pos, neg uint64) {
// posExpiration implements expirationController. Expiration happens only when
// free service is available.
func (f *clientPool) posExpiration(now mclock.AbsTime) float64 {
func (f *clientPool) posExpiration(now mclock.AbsTime) fixed64 {
f.expLock.RLock()
defer f.expLock.RUnlock()
@ -337,12 +343,12 @@ func (f *clientPool) posExpiration(now mclock.AbsTime) float64 {
dt = 0
}
dt /= mclock.AbsTime(time.Second)
return f.posExp + float64(dt)/float64(f.posExpTC)*f.freeRatio
return f.posExp + fixed64(float64(dt)/float64(f.posExpTC)*f.freeRatio)
}
// negExpiration implements expirationController. Expiration happens only when
// free service is available.
func (f *clientPool) negExpiration(now mclock.AbsTime) float64 {
func (f *clientPool) negExpiration(now mclock.AbsTime) fixed64 {
f.expLock.RLock()
defer f.expLock.RUnlock()
@ -354,7 +360,7 @@ func (f *clientPool) negExpiration(now mclock.AbsTime) float64 {
dt = 0
}
dt /= mclock.AbsTime(time.Second)
return f.negExp + float64(dt)/float64(f.negExpTC)*f.freeRatio
return f.negExp + fixed64(float64(dt)/float64(f.negExpTC)*f.freeRatio)
}
// totalTokenLimit returns the current token supply limit. Token prices are based
@ -686,7 +692,7 @@ func (f *clientPool) finalizeBalance(c *clientInfo, now mclock.AbsTime) {
for index, value := range []expiredValue{pos, neg} {
var (
id []byte
expiration float64
expiration fixed64
)
neg := index == 1
if !neg {

View file

@ -41,14 +41,15 @@ type expiredValue struct {
}
// value calculates the value at the given moment.
func (e expiredValue) value(logOffset float64) uint64 {
return uint64(float64(e.base) * math.Pow(2, float64(e.exp)-logOffset))
func (e expiredValue) value(logOffset fixed64) uint64 {
offset := uint64ToFixed64(e.exp) - logOffset
return uint64(float64(e.base) * offset.pow2Fixed())
}
// add adds a signed value at the given moment
func (e *expiredValue) add(amount int64, logOffset float64) int64 {
integer, frac := uint64(logOffset), logOffset-float64(uint64(logOffset))
factor := math.Pow(2, frac)
func (e *expiredValue) add(amount int64, logOffset fixed64) int64 {
integer, frac := logOffset.toUint64(), logOffset.fraction()
factor := frac.pow2Fixed()
base := factor * float64(amount)
if integer < e.exp {
base /= math.Pow(2, float64(e.exp-integer))
@ -93,3 +94,35 @@ func (e *expiredValue) subExp(a expiredValue) {
e.base = 0
}
}
// fixedFactor is the factor used by fixed64
const fixedFactor = 0x1000000
// fixed64 is a float64 wrapper that uses integer arithmetic
// to avoid precision loss in floating-point arithmetic.
type fixed64 int64
// uint64ToFixed64 converts uint64 integer to fixed64 format.
func uint64ToFixed64(f uint64) fixed64 {
return fixed64(f * fixedFactor)
}
// float64ToFixed64 converts float64 to fixed64 format.
func float64ToFixed64(f float64) fixed64 {
return fixed64(f * fixedFactor)
}
// toUint64 converts fixed64 format to uint64.
func (f64 fixed64) toUint64() uint64 {
return uint64(f64) / fixedFactor
}
// fraction returns the fraction of the fixed64.
func (f64 fixed64) fraction() fixed64 {
return f64 % fixedFactor
}
// pow2Fixed returns the 2 based pow of the fixed value.
func (f64 fixed64) pow2Fixed() float64 {
return math.Pow(2, float64(f64)/fixedFactor)
}

View file

@ -21,14 +21,14 @@ import "testing"
func TestValueExpiration(t *testing.T) {
var cases = []struct {
input expiredValue
timeOffset float64
timeOffset fixed64
expect uint64
}{
{expiredValue{base: 128, exp: 0}, 0, 128},
{expiredValue{base: 128, exp: 0}, 1, 64},
{expiredValue{base: 128, exp: 0}, 2, 32},
{expiredValue{base: 128, exp: 2}, 2, 128},
{expiredValue{base: 128, exp: 2}, 3, 64},
{expiredValue{base: 128, exp: 0}, uint64ToFixed64(0), 128},
{expiredValue{base: 128, exp: 0}, uint64ToFixed64(1), 64},
{expiredValue{base: 128, exp: 0}, uint64ToFixed64(2), 32},
{expiredValue{base: 128, exp: 2}, uint64ToFixed64(2), 128},
{expiredValue{base: 128, exp: 2}, uint64ToFixed64(3), 64},
}
for _, c := range cases {
if got := c.input.value(c.timeOffset); got != c.expect {
@ -41,29 +41,29 @@ func TestValueAddition(t *testing.T) {
var cases = []struct {
input expiredValue
addend int64
timeOffset float64
timeOffset fixed64
expect uint64
expectNet int64
}{
// Addition
{expiredValue{base: 128, exp: 0}, 128, 0, 256, 128},
{expiredValue{base: 128, exp: 2}, 128, 0, 640, 128},
{expiredValue{base: 128, exp: 0}, 128, uint64ToFixed64(0), 256, 128},
{expiredValue{base: 128, exp: 2}, 128, uint64ToFixed64(0), 640, 128},
// Addition with offset
{expiredValue{base: 128, exp: 0}, 128, 1, 192, 128},
{expiredValue{base: 128, exp: 2}, 128, 1, 384, 128},
{expiredValue{base: 128, exp: 2}, 128, 3, 192, 128},
{expiredValue{base: 128, exp: 0}, 128, uint64ToFixed64(1), 192, 128},
{expiredValue{base: 128, exp: 2}, 128, uint64ToFixed64(1), 384, 128},
{expiredValue{base: 128, exp: 2}, 128, uint64ToFixed64(3), 192, 128},
// Subtraction
{expiredValue{base: 128, exp: 0}, -64, 0, 64, -64},
{expiredValue{base: 128, exp: 0}, -128, 0, 0, -128},
{expiredValue{base: 128, exp: 0}, -192, 0, 0, -128},
{expiredValue{base: 128, exp: 0}, -64, uint64ToFixed64(0), 64, -64},
{expiredValue{base: 128, exp: 0}, -128, uint64ToFixed64(0), 0, -128},
{expiredValue{base: 128, exp: 0}, -192, uint64ToFixed64(0), 0, -128},
// Subtraction with offset
{expiredValue{base: 128, exp: 0}, -64, 1, 0, -64},
{expiredValue{base: 128, exp: 0}, -128, 1, 0, -64},
{expiredValue{base: 128, exp: 2}, -128, 1, 128, -128},
{expiredValue{base: 128, exp: 2}, -128, 2, 0, -128},
{expiredValue{base: 128, exp: 0}, -64, uint64ToFixed64(1), 0, -64},
{expiredValue{base: 128, exp: 0}, -128, uint64ToFixed64(1), 0, -64},
{expiredValue{base: 128, exp: 2}, -128, uint64ToFixed64(1), 128, -128},
{expiredValue{base: 128, exp: 2}, -128, uint64ToFixed64(2), 0, -128},
}
for _, c := range cases {
if net := c.input.add(c.addend, c.timeOffset); net != c.expectNet {
@ -79,13 +79,13 @@ func TestExpiredValueAddition(t *testing.T) {
var cases = []struct {
input expiredValue
another expiredValue
timeOffset float64
timeOffset fixed64
expect uint64
}{
{expiredValue{base: 128, exp: 0}, expiredValue{base: 128, exp: 0}, 0, 256},
{expiredValue{base: 128, exp: 1}, expiredValue{base: 128, exp: 0}, 0, 384},
{expiredValue{base: 128, exp: 0}, expiredValue{base: 128, exp: 1}, 0, 384},
{expiredValue{base: 128, exp: 0}, expiredValue{base: 128, exp: 0}, 1, 128},
{expiredValue{base: 128, exp: 0}, expiredValue{base: 128, exp: 0}, uint64ToFixed64(0), 256},
{expiredValue{base: 128, exp: 1}, expiredValue{base: 128, exp: 0}, uint64ToFixed64(0), 384},
{expiredValue{base: 128, exp: 0}, expiredValue{base: 128, exp: 1}, uint64ToFixed64(0), 384},
{expiredValue{base: 128, exp: 0}, expiredValue{base: 128, exp: 0}, uint64ToFixed64(1), 128},
}
for _, c := range cases {
c.input.addExp(c.another)
@ -99,13 +99,13 @@ func TestExpiredValueSubtraction(t *testing.T) {
var cases = []struct {
input expiredValue
another expiredValue
timeOffset float64
timeOffset fixed64
expect uint64
}{
{expiredValue{base: 128, exp: 0}, expiredValue{base: 128, exp: 0}, 0, 0},
{expiredValue{base: 128, exp: 0}, expiredValue{base: 128, exp: 1}, 0, 0},
{expiredValue{base: 128, exp: 1}, expiredValue{base: 128, exp: 0}, 0, 128},
{expiredValue{base: 128, exp: 1}, expiredValue{base: 128, exp: 0}, 1, 64},
{expiredValue{base: 128, exp: 0}, expiredValue{base: 128, exp: 0}, uint64ToFixed64(0), 0},
{expiredValue{base: 128, exp: 0}, expiredValue{base: 128, exp: 1}, uint64ToFixed64(0), 0},
{expiredValue{base: 128, exp: 1}, expiredValue{base: 128, exp: 0}, uint64ToFixed64(0), 128},
{expiredValue{base: 128, exp: 1}, expiredValue{base: 128, exp: 0}, uint64ToFixed64(1), 64},
}
for _, c := range cases {
c.input.subExp(c.another)