amount_test.go raw

   1  package amt_test
   2  
   3  import (
   4  	"github.com/p9c/p9/pkg/amt"
   5  	"math"
   6  	"testing"
   7  )
   8  
   9  func TestAmountCreation(t *testing.T) {
  10  	tests := []struct {
  11  		name     string
  12  		amount   float64
  13  		valid    bool
  14  		expected amt.Amount
  15  	}{
  16  		// Positive tests.
  17  		{
  18  			name:     "zero",
  19  			amount:   0,
  20  			valid:    true,
  21  			expected: 0,
  22  		},
  23  		{
  24  			name:     "max producible",
  25  			amount:   21e6,
  26  			valid:    true,
  27  			expected: amt.MaxSatoshi,
  28  		},
  29  		{
  30  			name:     "min producible",
  31  			amount:   -21e6,
  32  			valid:    true,
  33  			expected: -amt.MaxSatoshi,
  34  		},
  35  		{
  36  			name:     "exceeds max producible",
  37  			amount:   21e6 + 1e-8,
  38  			valid:    true,
  39  			expected: amt.MaxSatoshi + 1,
  40  		},
  41  		{
  42  			name:     "exceeds min producible",
  43  			amount:   -21e6 - 1e-8,
  44  			valid:    true,
  45  			expected: -amt.MaxSatoshi - 1,
  46  		},
  47  		{
  48  			name:     "one hundred",
  49  			amount:   100,
  50  			valid:    true,
  51  			expected: 100 * amt.SatoshiPerBitcoin,
  52  		},
  53  		{
  54  			name:     "fraction",
  55  			amount:   0.01234567,
  56  			valid:    true,
  57  			expected: 1234567,
  58  		},
  59  		{
  60  			name:     "rounding up",
  61  			amount:   54.999999999999943157,
  62  			valid:    true,
  63  			expected: 55 * amt.SatoshiPerBitcoin,
  64  		},
  65  		{
  66  			name:     "rounding down",
  67  			amount:   55.000000000000056843,
  68  			valid:    true,
  69  			expected: 55 * amt.SatoshiPerBitcoin,
  70  		},
  71  		// Negative tests.
  72  		{
  73  			name:   "not-a-number",
  74  			amount: math.NaN(),
  75  			valid:  false,
  76  		},
  77  		{
  78  			name:   "-infinity",
  79  			amount: math.Inf(-1),
  80  			valid:  false,
  81  		},
  82  		{
  83  			name:   "+infinity",
  84  			amount: math.Inf(1),
  85  			valid:  false,
  86  		},
  87  	}
  88  	for _, test := range tests {
  89  		a, e := amt.NewAmount(test.amount)
  90  		switch {
  91  		case test.valid && e != nil:
  92  			t.Errorf("%v: Positive test Amount creation failed with: %v", test.name, e)
  93  			continue
  94  		case !test.valid && e == nil:
  95  			t.Errorf("%v: Negative test Amount creation succeeded (value %v) when should fail", test.name, a)
  96  			continue
  97  		}
  98  		if a != test.expected {
  99  			t.Errorf("%v: Created amount %v does not match expected %v", test.name, a, test.expected)
 100  			continue
 101  		}
 102  	}
 103  }
 104  func TestAmountUnitConversions(t *testing.T) {
 105  	tests := []struct {
 106  		name      string
 107  		amount    amt.Amount
 108  		unit      amt.Unit
 109  		converted float64
 110  		s         string
 111  	}{
 112  		{
 113  			name:      "MDUO",
 114  			amount:    amt.MaxSatoshi,
 115  			unit:      amt.MegaDUO,
 116  			converted: 21,
 117  			s:         "21 MDUO",
 118  		},
 119  		{
 120  			name:      "kDUO",
 121  			amount:    44433322211100,
 122  			unit:      amt.KiloDUO,
 123  			converted: 444.33322211100,
 124  			s:         "444.333222111 kDUO",
 125  		},
 126  		{
 127  			name:      "DUO",
 128  			amount:    44433322211100,
 129  			unit:      amt.DUO,
 130  			converted: 444333.22211100,
 131  			s:         "444333.222111 DUO",
 132  		},
 133  		{
 134  			name:      "mDUO",
 135  			amount:    44433322211100,
 136  			unit:      amt.MilliDUO,
 137  			converted: 444333222.11100,
 138  			s:         "444333222.111 mDUO",
 139  		},
 140  		{
 141  			name:      "μDUO",
 142  			amount:    44433322211100,
 143  			unit:      amt.MicroDUO,
 144  			converted: 444333222111.00,
 145  			s:         "444333222111 μDUO",
 146  		},
 147  		{
 148  			name:      "satoshi",
 149  			amount:    44433322211100,
 150  			unit:      amt.Satoshi,
 151  			converted: 44433322211100,
 152  			s:         "44433322211100 Satoshi",
 153  		},
 154  		{
 155  			name:      "non-standard unit",
 156  			amount:    44433322211100,
 157  			unit:      amt.Unit(-1),
 158  			converted: 4443332.2211100,
 159  			s:         "4443332.22111 1e-1 DUO",
 160  		},
 161  	}
 162  	for _, test := range tests {
 163  		f := test.amount.ToUnit(test.unit)
 164  		if f != test.converted {
 165  			t.Errorf("%v: converted value %v does not match expected %v", test.name, f, test.converted)
 166  			continue
 167  		}
 168  		s := test.amount.Format(test.unit)
 169  		if s != test.s {
 170  			t.Errorf("%v: format '%v' does not match expected '%v'", test.name, s, test.s)
 171  			continue
 172  		}
 173  		// Verify that Amount.ToDUO works as advertised.
 174  		f1 := test.amount.ToUnit(amt.DUO)
 175  		f2 := test.amount.ToDUO()
 176  		if f1 != f2 {
 177  			t.Errorf("%v: ToDUO does not match ToUnit(AmountDUO): %v != %v", test.name, f1, f2)
 178  		}
 179  		// Verify that Amount.String works as advertised.
 180  		s1 := test.amount.Format(amt.DUO)
 181  		s2 := test.amount.String()
 182  		if s1 != s2 {
 183  			t.Errorf("%v: String does not match Format(AmountBitcoin): %v != %v", test.name, s1, s2)
 184  		}
 185  	}
 186  }
 187  func TestAmountMulF64(t *testing.T) {
 188  	tests := []struct {
 189  		name string
 190  		amt  amt.Amount
 191  		mul  float64
 192  		res  amt.Amount
 193  	}{
 194  		{
 195  			name: "Multiply 0.1 DUO by 2",
 196  			amt:  100e5, // 0.1 DUO
 197  			mul:  2,
 198  			res:  200e5, // 0.2 DUO
 199  		},
 200  		{
 201  			name: "Multiply 0.2 DUO by 0.02",
 202  			amt:  200e5, // 0.2 DUO
 203  			mul:  1.02,
 204  			res:  204e5, // 0.204 DUO
 205  		},
 206  		{
 207  			name: "Multiply 0.1 DUO by -2",
 208  			amt:  100e5, // 0.1 DUO
 209  			mul:  -2,
 210  			res:  -200e5, // -0.2 DUO
 211  		},
 212  		{
 213  			name: "Multiply 0.2 DUO by -0.02",
 214  			amt:  200e5, // 0.2 DUO
 215  			mul:  -1.02,
 216  			res:  -204e5, // -0.204 DUO
 217  		},
 218  		{
 219  			name: "Multiply -0.1 DUO by 2",
 220  			amt:  -100e5, // -0.1 DUO
 221  			mul:  2,
 222  			res:  -200e5, // -0.2 DUO
 223  		},
 224  		{
 225  			name: "Multiply -0.2 DUO by 0.02",
 226  			amt:  -200e5, // -0.2 DUO
 227  			mul:  1.02,
 228  			res:  -204e5, // -0.204 DUO
 229  		},
 230  		{
 231  			name: "Multiply -0.1 DUO by -2",
 232  			amt:  -100e5, // -0.1 DUO
 233  			mul:  -2,
 234  			res:  200e5, // 0.2 DUO
 235  		},
 236  		{
 237  			name: "Multiply -0.2 DUO by -0.02",
 238  			amt:  -200e5, // -0.2 DUO
 239  			mul:  -1.02,
 240  			res:  204e5, // 0.204 DUO
 241  		},
 242  		{
 243  			name: "Round down",
 244  			amt:  49, // 49 Satoshis
 245  			mul:  0.01,
 246  			res:  0,
 247  		},
 248  		{
 249  			name: "Round up",
 250  			amt:  50, // 50 Satoshis
 251  			mul:  0.01,
 252  			res:  1, // 1 Satoshi
 253  		},
 254  		{
 255  			name: "Multiply by 0.",
 256  			amt:  1e8, // 1 DUO
 257  			mul:  0,
 258  			res:  0, // 0 DUO
 259  		},
 260  		{
 261  			name: "Multiply 1 by 0.5.",
 262  			amt:  1, // 1 Satoshi
 263  			mul:  0.5,
 264  			res:  1, // 1 Satoshi
 265  		},
 266  		{
 267  			name: "Multiply 100 by 66%.",
 268  			amt:  100, // 100 Satoshis
 269  			mul:  0.66,
 270  			res:  66, // 66 Satoshis
 271  		},
 272  		{
 273  			name: "Multiply 100 by 66.6%.",
 274  			amt:  100, // 100 Satoshis
 275  			mul:  0.666,
 276  			res:  67, // 67 Satoshis
 277  		},
 278  		{
 279  			name: "Multiply 100 by 2/3.",
 280  			amt:  100, // 100 Satoshis
 281  			mul:  2.0 / 3,
 282  			res:  67, // 67 Satoshis
 283  		},
 284  	}
 285  	for _, test := range tests {
 286  		a := test.amt.MulF64(test.mul)
 287  		if a != test.res {
 288  			t.Errorf("%v: expected %v got %v", test.name, test.res, a)
 289  		}
 290  	}
 291  }
 292