github.com/deso-protocol/core@v1.2.9/lib/deso_math.go (about)

     1  package lib
     2  
     3  import (
     4  	"fmt"
     5  	"math/big"
     6  )
     7  
     8  // This library implements basic float functions using big.Float objects.
     9  // This is necessary in order to ensure interoperability across different
    10  // machines. If we instead were to use float64's for our computations
    11  // naively, then machines with different rounding rules or different
    12  // precision for intermediate values could produce different results that
    13  // would cause blockchain forks. Having our own library ensures not only
    14  // that such forks can't occur but also makes it so that implementing a
    15  // node in another language is fairly straightforward because all of the
    16  // operations are implemented in software.
    17  
    18  const (
    19  	FloatPrecision uint = 53
    20  )
    21  
    22  func NewFloat() *big.Float {
    23  	// We force all calculations be done at a particular precision. This keeps
    24  	// all nodes in sync and avoids consensus issues around one node using a
    25  	// different precision than another node. We also force the same rounding
    26  	// mode for all calculations.
    27  	return big.NewFloat(0.0).SetPrec(FloatPrecision).SetMode(big.ToNearestEven)
    28  }
    29  
    30  func IntSub(a *big.Int, b *big.Int) *big.Int {
    31  	// TODO(performance): We should do this without creating an int copy, but
    32  	// this is easier to understand and deal with for now.
    33  	return big.NewInt(0).Sub(a, b)
    34  }
    35  
    36  func IntMul(a *big.Int, b *big.Int) *big.Int {
    37  	// TODO(performance): We should do this without creating an int copy, but
    38  	// this is easier to understand and deal with for now.
    39  	return big.NewInt(0).Mul(a, b)
    40  }
    41  
    42  func IntDiv(a *big.Int, b *big.Int) *big.Int {
    43  	// TODO(performance): We should do this without creating an int copy, but
    44  	// this is easier to understand and deal with for now.
    45  	return big.NewInt(0).Quo(a, b)
    46  }
    47  
    48  func IntAdd(a *big.Int, b *big.Int) *big.Int {
    49  	// TODO(performance): We should do this without creating an int copy, but
    50  	// this is easier to understand and deal with for now.
    51  	return big.NewInt(0).Add(a, b)
    52  }
    53  
    54  func Sub(a *big.Float, b *big.Float) *big.Float {
    55  	// TODO(performance): This code currently calls NewFloat() too often. It
    56  	// does this in order to make the code easier to read but if it ever becomes
    57  	// an issue, the superfluous calls to NewFloat() should be a quick win.
    58  	return NewFloat().Sub(a, b)
    59  }
    60  
    61  func Mul(a *big.Float, b *big.Float) *big.Float {
    62  	// TODO(performance): This code currently calls NewFloat() too often. It
    63  	// does this in order to make the code easier to read but if it ever becomes
    64  	// an issue, the superfluous calls to NewFloat() should be a quick win.
    65  	return NewFloat().Mul(a, b)
    66  }
    67  
    68  func Div(a *big.Float, b *big.Float) *big.Float {
    69  	// TODO(performance): This code currently calls NewFloat() too often. It
    70  	// does this in order to make the code easier to read but if it ever becomes
    71  	// an issue, the superfluous calls to NewFloat() should be a quick win.
    72  	return NewFloat().Quo(a, b)
    73  }
    74  
    75  func Add(a *big.Float, b *big.Float) *big.Float {
    76  	// TODO(performance): This code currently calls NewFloat() too often. It
    77  	// does this in order to make the code easier to read but if it ever becomes
    78  	// an issue, the superfluous calls to NewFloat() should be a quick win.
    79  	return NewFloat().Add(a, b)
    80  }
    81  
    82  var (
    83  	// Constants for BigFloatLog
    84  	bigLn2Hi          = NewFloat().SetFloat64(6.93147180369123816490e-01) /* 3fe62e42 fee00000 */
    85  	bigLn2Lo          = NewFloat().SetFloat64(1.90821492927058770002e-10) /* 3dea39ef 35793c76 */
    86  	bigL1             = NewFloat().SetFloat64(6.666666666666735130e-01)   /* 3FE55555 55555593 */
    87  	bigL2             = NewFloat().SetFloat64(3.999999999940941908e-01)   /* 3FD99999 9997FA04 */
    88  	bigL3             = NewFloat().SetFloat64(2.857142874366239149e-01)   /* 3FD24924 94229359 */
    89  	bigL4             = NewFloat().SetFloat64(2.222219843214978396e-01)   /* 3FCC71C5 1D8E78AF */
    90  	bigL5             = NewFloat().SetFloat64(1.818357216161805012e-01)   /* 3FC74664 96CB03DE */
    91  	bigL6             = NewFloat().SetFloat64(1.531383769920937332e-01)   /* 3FC39A09 D078C69F */
    92  	bigL7             = NewFloat().SetFloat64(1.479819860511658591e-01)   /* 3FC2F112 DF3E5244 */
    93  	bigSqrt2          = NewFloat().SetFloat64(1.41421356237309504880168872420969807856967187537694807317667974)
    94  	bigHalf           = NewFloat().SetFloat64(.5)
    95  	bigNegativeOneOne = NewFloat().SetUint64(1)
    96  	bigOne            = NewFloat().SetUint64(1)
    97  	bigTwo            = NewFloat().SetUint64(2)
    98  	bigSqrt2Over2     = NewFloat().Quo(bigSqrt2, bigTwo)
    99  
   100  	// Constants for BigFloatExpMulti
   101  	bigP1 = NewFloat().SetFloat64(1.66666666666666657415e-01)  /* 0x3FC55555; 0x55555555 */
   102  	bigP2 = NewFloat().SetFloat64(-2.77777777770155933842e-03) /* 0xBF66C16C; 0x16BEBD93 */
   103  	bigP3 = NewFloat().SetFloat64(6.61375632143793436117e-05)  /* 0x3F11566A; 0xAF25DE2C */
   104  	bigP4 = NewFloat().SetFloat64(-1.65339022054652515390e-06) /* 0xBEBBBD41; 0xC5D26BF1 */
   105  	bigP5 = NewFloat().SetFloat64(4.13813679705723846039e-08)  /* 0x3E663769; 0x72BEA4D0 */
   106  
   107  	// Constants for BigFloatExp
   108  	bigZero  = NewFloat().SetUint64(0)
   109  	bigLog2e = NewFloat().SetFloat64(1.44269504088896338700e+00)
   110  )
   111  
   112  // Log returns the natural logarithm of x.
   113  func BigFloatLog(x *big.Float) *big.Float {
   114  	// special cases
   115  	// TODO: We should make the special cases work at some point.
   116  	switch {
   117  	case x.IsInf():
   118  		panic(fmt.Sprintf("BigFloatLog: Cannot take log of an infinite number: %v", x))
   119  	case x.Sign() <= 0:
   120  		panic(fmt.Sprintf("BigFloatLog: Cannot take log of a number <= 0: %v", x))
   121  	}
   122  
   123  	// Reduce
   124  	f1 := NewFloat()
   125  	ki := x.MantExp(f1)
   126  	if f1.Cmp(bigSqrt2Over2) < 0 {
   127  		f1 = Mul(f1, bigTwo)
   128  		ki--
   129  	}
   130  	f := Sub(f1, bigOne)
   131  	k := NewFloat().SetInt64(int64(ki))
   132  
   133  	// Compute
   134  	twoPlusF := Add(bigTwo, f)
   135  	s := Div(f, twoPlusF)
   136  	s2 := Mul(s, s)
   137  	s4 := Mul(s2, s2)
   138  
   139  	t1 := Mul(s2, (Add(bigL1, Mul(s4, (Add(bigL3, Mul(s4, (Add(bigL5, Mul(s4, bigL7))))))))))
   140  	t2 := Mul(s4, Add(bigL2, Mul(s4, Add(bigL4, Mul(s4, bigL6)))))
   141  	R := Add(t1, t2)
   142  	hfsq := Mul(bigHalf, NewFloat().Mul(f, f))
   143  
   144  	return Sub(Mul(k, bigLn2Hi), Sub(Sub(hfsq, (Add(Mul(s, Add(hfsq, R)), Mul(k, bigLn2Lo)))), f))
   145  }
   146  
   147  // Log2 returns the binary logarithm of x.
   148  // The special cases are the same as for Log.
   149  func BigFloatLog2(x *big.Float) *big.Float {
   150  	bigTwo := NewFloat().SetUint64(2)
   151  	return Div(BigFloatLog(x), BigFloatLog(bigTwo))
   152  }
   153  
   154  func BigFloatExpMulti(hi, lo *big.Float, k int64) *big.Float {
   155  	r := Sub(hi, lo)
   156  	t := Mul(r, r)
   157  	c := Sub(r, Mul(t, (Add(bigP1, Mul(t, (Add(bigP2, Mul(t, (Add(bigP3, Mul(t, (Add(bigP4, Mul(t, bigP5))))))))))))))
   158  	y := Sub(bigOne, (Sub((Sub(lo, Div((Mul(r, c)), (Sub(bigTwo, c))))), hi)))
   159  
   160  	// TODO: make sure Ldexp can handle boundary k
   161  	return NewFloat().SetMantExp(y, int(k))
   162  }
   163  
   164  // Exp returns a big.Float representation of exp(z).
   165  func BigFloatExp(z *big.Float) *big.Float {
   166  	if z.IsInf() {
   167  		panic("BigFloatExp: Cannot call exp with infinity")
   168  	}
   169  
   170  	// reduce; computed as r = hi - lo for extra precision.
   171  	var k int64
   172  	switch {
   173  	case z.Cmp(bigZero) < 0:
   174  		k, _ = Sub(Mul(bigLog2e, z), bigHalf).Int64()
   175  	case z.Cmp(bigZero) > 0:
   176  		k, _ = Add(Mul(bigLog2e, z), bigHalf).Int64()
   177  	}
   178  	hi := Sub(z, Mul(NewFloat().SetInt64(k), bigLn2Hi))
   179  	lo := Mul(NewFloat().SetInt64(k), bigLn2Lo)
   180  
   181  	// compute
   182  	return BigFloatExpMulti(hi, lo, k)
   183  }
   184  
   185  // Pow returns a big.Float representation of z**w.
   186  func BigFloatPow(z *big.Float, w *big.Float) *big.Float {
   187  	if z.Sign() < 0 {
   188  		panic("Pow: negative base")
   189  	}
   190  	if z.Cmp(bigZero) == 0 {
   191  		return bigZero
   192  	}
   193  
   194  	// Pow(z, 0) = 1.0
   195  	if w.Sign() == 0 {
   196  		return bigOne
   197  	}
   198  
   199  	// Pow(z, 1) = z
   200  	// Pow(+Inf, n) = +Inf
   201  	if w.Cmp(bigOne) == 0 || z.IsInf() {
   202  		return NewFloat().Copy(z)
   203  	}
   204  
   205  	// Pow(z, -w) = 1 / Pow(z, w)
   206  	if w.Sign() < 0 {
   207  		x := NewFloat()
   208  		zExt := NewFloat().Copy(z).SetPrec(z.Prec() + 64)
   209  		wNeg := NewFloat().Neg(w)
   210  		return x.Quo(bigOne, BigFloatPow(zExt, wNeg)).SetPrec(z.Prec())
   211  	}
   212  
   213  	// compute w**z as exp(z log(w))
   214  	x := NewFloat().SetPrec(z.Prec() + 64)
   215  	logZ := BigFloatLog(NewFloat().Copy(z).SetPrec(z.Prec() + 64))
   216  	x.Mul(w, logZ)
   217  	x = BigFloatExp(x)
   218  	return x.SetPrec(z.Prec())
   219  }