├── .travis.yml ├── examples ├── div.go └── wallet.go ├── moving_average.go ├── LICENSE ├── README.md ├── fpd.go └── fpd_test.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.0 5 | - 1.1 6 | - tip 7 | 8 | install: 9 | - go build . 10 | 11 | script: 12 | - go test -v 13 | -------------------------------------------------------------------------------- /examples/div.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/oguzbilgic/fpd" 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | // 166.004 10 | a := fpd.New(18275499, -6) 11 | 12 | // 1.006 13 | b := fpd.New(1, -1) 14 | c := a.Div(b) 15 | d := c.Mul(fpd.New(100, 0)) 16 | 17 | fmt.Println(c.FormattedString()) 18 | fmt.Println(d.FormattedString()) 19 | } 20 | -------------------------------------------------------------------------------- /moving_average.go: -------------------------------------------------------------------------------- 1 | package fpd 2 | 3 | type MovingAverage struct { 4 | samples []*Decimal 5 | scale int 6 | capacity int 7 | } 8 | 9 | func NewMovingAverage(capacity int, scale int) *MovingAverage { 10 | return &MovingAverage{ 11 | scale: scale, 12 | capacity: capacity, 13 | } 14 | } 15 | 16 | func (ma *MovingAverage) Append(sample *Decimal) { 17 | if len(ma.samples) == ma.capacity { 18 | ma.samples = append(ma.samples[1:], sample) 19 | } else { 20 | ma.samples = append(ma.samples, sample) 21 | } 22 | } 23 | 24 | func (ma *MovingAverage) Calculate() *Decimal { 25 | sum := New(0, ma.scale) 26 | for _, sample := range ma.samples { 27 | sum = sum.Add(sample) 28 | } 29 | 30 | return sum.Div(New(int64(len(ma.samples)), 0)) 31 | } 32 | 33 | func (ma *MovingAverage) Capacity() int { 34 | return ma.capacity 35 | } 36 | 37 | func (ma *MovingAverage) Size() int { 38 | return len(ma.samples) 39 | } 40 | -------------------------------------------------------------------------------- /examples/wallet.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/oguzbilgic/fpd" 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | // Buy price of the security: $136.02 10 | buyPrice := fpd.New(13602000, -5) 11 | 12 | // Sell price of the security: $137.699 13 | sellPrice := fpd.New(13769900, -5) 14 | 15 | // Volume traded: 0.01 16 | volume := fpd.New(1000000, -8) 17 | 18 | // Trade fee percentage: 0.6% 19 | feePer := fpd.New(6, -3) 20 | 21 | buyCost := buyPrice.Mul(volume) 22 | buyFee := buyPrice.Mul(volume).Mul(feePer) 23 | sellRevenue := sellPrice.Mul(volume) 24 | sellFee := sellPrice.Mul(volume).Mul(feePer) 25 | 26 | // Initall account balance: $2.00000 27 | balance := fpd.New(200000, -5) 28 | 29 | balance = balance.Sub(buyCost) 30 | balance = balance.Sub(buyFee) 31 | balance = balance.Add(sellRevenue) 32 | balance = balance.Sub(sellFee) 33 | 34 | // Final balance 35 | fmt.Println(balance.FormattedString()) 36 | // Did this trade turn into profit? :) 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Oguz Bilgic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # *fpd.Decimal [![Build Status](https://travis-ci.org/oguzbilgic/fpd.png?branch=master)](https://travis-ci.org/oguzbilgic/fpd) 2 | 3 | Package implements fixed-point decimal 4 | 5 | ## Usage 6 | 7 | ```go 8 | package main 9 | 10 | import "github.com/oguzbilgic/fpd" 11 | 12 | func main() { 13 | // Buy price of the security: $136.02 14 | buyPrice := fpd.New(13602000, -5) 15 | 16 | // Sell price of the security: $137.699 17 | sellPrice := fpd.New(13769900, -5) 18 | 19 | // Volume traded: 0.01 20 | volume := fpd.New(1000000, -8) 21 | 22 | // Trade fee percentage: 0.6% 23 | feePer := fpd.New(6, -3) 24 | 25 | buyCost := buyPrice.Mul(volume) 26 | buyFee := buyPrice.Mul(volume).Mul(feePer) 27 | sellRevenue := sellPrice.Mul(volume) 28 | sellFee := sellPrice.Mul(volume).Mul(feePer) 29 | 30 | // Initall account balance: $2.00000 31 | balance := fpd.New(200000, -5) 32 | 33 | balance = balance.Sub(buyCost) 34 | balance = balance.Sub(buyFee) 35 | balance = balance.Add(sellRevenue) 36 | balance = balance.Sub(sellFee) 37 | 38 | // Final balance 39 | fmt.Println(balance) 40 | // Did this trade turn into profit? :) 41 | } 42 | ``` 43 | 44 | ## Documentation 45 | 46 | http://godoc.org/github.com/oguzbilgic/fpd 47 | 48 | ## License 49 | 50 | The MIT License (MIT) 51 | -------------------------------------------------------------------------------- /fpd.go: -------------------------------------------------------------------------------- 1 | // Package implements a fixed-point decimal 2 | package fpd 3 | 4 | import ( 5 | "errors" 6 | "fmt" 7 | "math" 8 | "math/big" 9 | "strings" 10 | ) 11 | 12 | // Decimal represents a fixed-point decimal. 13 | type Decimal struct { 14 | value *big.Int 15 | scale int 16 | } 17 | 18 | // New returns a new fixed-point decimal 19 | func New(value int64, scale int) *Decimal { 20 | return &Decimal{big.NewInt(value), scale} 21 | } 22 | 23 | // NewFromString returns a new fixed-point decimal based 24 | // on the given string 25 | func NewFromString(value string, scale int) (*Decimal, error) { 26 | dValue := big.NewInt(0) 27 | _, ok := dValue.SetString(value, 10) 28 | if !ok { 29 | return nil, errors.New("can't convert to decimal") 30 | } 31 | 32 | return &Decimal{dValue, scale}, nil 33 | } 34 | 35 | func NewFromFloat(value float64, scale int) *Decimal { 36 | scaleMul := math.Pow(10, -float64(scale)) 37 | intValue := int64(value * scaleMul) 38 | dValue := big.NewInt(intValue) 39 | 40 | return &Decimal{dValue, scale} 41 | } 42 | 43 | // Rescale returns a rescaled version of the decimal. Returned 44 | // decimal may be less precise if the given scale is bigger 45 | // than the initial scale of the Decimal 46 | // 47 | // Example: 48 | // 49 | // d := New(12345, -4) 50 | // d2 := d.rescale(-1) 51 | // d3 := d2.rescale(-4) 52 | // println(d1) 53 | // println(d2) 54 | // println(d3) 55 | // 56 | // Output: 57 | // 58 | // 1.2345 59 | // 1.2 60 | // 1.2000 61 | // 62 | func (d Decimal) rescale(scale int) *Decimal { 63 | diff := int(math.Abs(float64(scale - d.scale))) 64 | value := big.NewInt(0).Set(d.value) 65 | ten := big.NewInt(10) 66 | 67 | for diff > 0 { 68 | if scale > d.scale { 69 | value = value.Quo(value, ten) 70 | } else if scale < d.scale { 71 | value = value.Mul(value, ten) 72 | } 73 | 74 | diff-- 75 | } 76 | 77 | return &Decimal{value, scale} 78 | } 79 | 80 | func (d *Decimal) Abs() *Decimal { 81 | d2Value := big.NewInt(0).Abs(d.value) 82 | return &Decimal{d2Value, d.scale} 83 | } 84 | 85 | // Add adds d to d2 and return d3 86 | func (d *Decimal) Add(d2 *Decimal) *Decimal { 87 | d3Value := big.NewInt(0).Add(d.value, d2.rescale(d.scale).value) 88 | return &Decimal{d3Value, d.scale} 89 | } 90 | 91 | // Sub subtracts d2 from d and returns d3 92 | func (d *Decimal) Sub(d2 *Decimal) *Decimal { 93 | baseScale := smallestOf(d.scale, d2.scale) 94 | rd := d.rescale(baseScale) 95 | rd2 := d2.rescale(baseScale) 96 | 97 | d3Value := big.NewInt(0).Sub(rd.value, rd2.value) 98 | d3 := &Decimal{d3Value, baseScale} 99 | return d3.rescale(d.scale) 100 | } 101 | 102 | // Mul multiplies d with d2 and returns d3 103 | func (d *Decimal) Mul(d2 *Decimal) *Decimal { 104 | baseScale := smallestOf(d.scale, d2.scale) 105 | rd := d.rescale(baseScale) 106 | rd2 := d2.rescale(baseScale) 107 | 108 | d3Value := big.NewInt(0).Mul(rd.value, rd2.value) 109 | d3 := &Decimal{d3Value, 2 * baseScale} 110 | return d3.rescale(d.scale) 111 | } 112 | 113 | // Mul divides d by d2 and returns d3 114 | func (d *Decimal) Div(d2 *Decimal) *Decimal { 115 | baseScale := -int(math.Pow(float64(smallestOf(d.scale, d2.scale)), 2)) 116 | 117 | rd := d.rescale(baseScale + d.scale) 118 | rd2 := d2.rescale(baseScale) 119 | 120 | d3Value := big.NewInt(0).Div(rd.value, rd2.value) 121 | 122 | d3 := &Decimal{d3Value, d.scale} 123 | return d3.rescale(d.scale) 124 | } 125 | 126 | // Cmp compares x and y and returns -1, 0 or 1 127 | // 128 | // Example 129 | // 130 | //-1 if x < y 131 | // 0 if x == y 132 | //+1 if x > y 133 | // 134 | func (d *Decimal) Cmp(d2 *Decimal) int { 135 | smallestScale := smallestOf(d.scale, d2.scale) 136 | rd := d.rescale(smallestScale) 137 | rd2 := d2.rescale(smallestScale) 138 | 139 | return rd.value.Cmp(rd2.value) 140 | } 141 | 142 | func (d *Decimal) Scale() int { 143 | return d.scale 144 | } 145 | 146 | // String returns the string representatino of the decimal 147 | // 148 | // Example: 149 | // 150 | // d := New(-12345, -3) 151 | // println(d.String()) 152 | // 153 | // Output: 154 | // 155 | // -12345 156 | // 157 | func (d *Decimal) String() string { 158 | return d.value.String() 159 | } 160 | 161 | // String returns the string representatino of the decimal 162 | // with the fixed point 163 | // 164 | // Example: 165 | // 166 | // d := New(-12345, -3) 167 | // println(d.String()) 168 | // 169 | // Output: 170 | // 171 | // -12.345 172 | // 173 | func (d *Decimal) FormattedString() string { 174 | if d.scale >= 0 { 175 | return d.rescale(0).value.String() 176 | } 177 | 178 | abs := big.NewInt(0).Abs(d.value) 179 | str := abs.String() 180 | 181 | var a, b string 182 | if len(str) >= -d.scale { 183 | a = str[:len(str)+d.scale] 184 | b = str[len(str)+d.scale:] 185 | } else { 186 | num0s := -d.scale - len(str) 187 | b = strings.Repeat("0", num0s) + str 188 | } 189 | 190 | if a == "" { 191 | a = "0" 192 | } 193 | 194 | if d.value.Sign() < 0 { 195 | return fmt.Sprintf("-%v.%v", a, b) 196 | } 197 | 198 | return fmt.Sprintf("%v.%v", a, b) 199 | } 200 | 201 | // StringScaled first scales the decimal then calls .String() on it. 202 | func (d *Decimal) StringScaled(scale int) string { 203 | return d.rescale(scale).String() 204 | } 205 | 206 | func smallestOf(x, y int) int { 207 | if x >= y { 208 | return y 209 | } 210 | return x 211 | } 212 | -------------------------------------------------------------------------------- /fpd_test.go: -------------------------------------------------------------------------------- 1 | package fpd 2 | 3 | import "testing" 4 | 5 | func TestNewFromString1(t *testing.T) { 6 | a, err := NewFromString("1234", -3) 7 | 8 | if err != nil { 9 | t.Errorf("error") 10 | } 11 | 12 | if a.String() != "1234" { 13 | t.Errorf("error") 14 | } 15 | } 16 | 17 | func TestNewFromString2(t *testing.T) { 18 | a, err := NewFromString("-1234", -3) 19 | 20 | if err != nil { 21 | t.Errorf("error") 22 | } 23 | 24 | if a.String() != "-1234" { 25 | t.Errorf("error") 26 | } 27 | } 28 | 29 | func TestNewFromString3(t *testing.T) { 30 | _, err := NewFromString("qwert", -3) 31 | 32 | if err == nil { 33 | t.Errorf("error") 34 | } 35 | } 36 | 37 | func TestNewFromFloat1(t *testing.T) { 38 | a := NewFromFloat(-123.4, -3) 39 | 40 | if a.FormattedString() != "-123.400" { 41 | t.Errorf("error") 42 | } 43 | 44 | if a.String() != "-123400" { 45 | t.Errorf("error") 46 | } 47 | } 48 | 49 | func TestNewFromFloat3(t *testing.T) { 50 | a := NewFromFloat(123.412345, 1) 51 | 52 | if a.String() != "12" { 53 | t.Errorf(a.String() + " != 12") 54 | } 55 | 56 | if a.FormattedString() != "120" { 57 | t.Errorf(a.FormattedString() + " != 120") 58 | } 59 | } 60 | 61 | func TestNewFromFloat2(t *testing.T) { 62 | a := NewFromFloat(123.412, 0) 63 | 64 | if a.String() != "123" { 65 | t.Errorf(a.String() + " != 123") 66 | } 67 | 68 | if a.FormattedString() != "123" { 69 | t.Errorf(a.FormattedString() + " != 123") 70 | } 71 | } 72 | 73 | func TestDecimal_Scale(t *testing.T) { 74 | a := New(1234, -3) 75 | if a.Scale() != -3 { 76 | t.Errorf("error") 77 | } 78 | } 79 | 80 | func TestDecimal_recale1(t *testing.T) { 81 | a := New(1234, -3).rescale(-5) 82 | if a.String() != "123400" { 83 | t.Errorf(a.String() + " != 123400") 84 | } 85 | } 86 | 87 | func TestDecimal_recale2(t *testing.T) { 88 | a := New(1234, -3).rescale(0) 89 | if a.String() != "1" { 90 | t.Errorf("error") 91 | } 92 | } 93 | 94 | func TestDecimal_recale3(t *testing.T) { 95 | a := New(1234, 3).rescale(0) 96 | if a.String() != "1234000" { 97 | t.Errorf("error") 98 | } 99 | } 100 | 101 | func TestDecimal_recale4(t *testing.T) { 102 | a := New(1234, 3).rescale(5) 103 | if a.String() != "12" { 104 | t.Errorf("error") 105 | } 106 | } 107 | 108 | func TestDecimal_recale5(t *testing.T) { 109 | a := New(1234, 3) 110 | _ = a.rescale(5) 111 | if a.String() != "1234" { 112 | t.Errorf("error") 113 | } 114 | } 115 | 116 | func TestDecimal_Abs1(t *testing.T) { 117 | a := New(-1234, -4) 118 | b := New(1234, -4) 119 | 120 | c := a.Abs() 121 | if c.Cmp(b) != 0 { 122 | t.Errorf("error") 123 | } 124 | } 125 | 126 | func TestDecimal_Abs2(t *testing.T) { 127 | a := New(-1234, -4) 128 | b := New(1234, -4) 129 | 130 | c := b.Abs() 131 | if c.Cmp(a) == 0 { 132 | t.Errorf("error") 133 | } 134 | } 135 | 136 | func TestDecimal_Add1(t *testing.T) { 137 | a := New(1234, -4) 138 | b := New(9876, 3) 139 | 140 | c := a.Add(b) 141 | if c.String() != "98760001234" { 142 | t.Errorf("error") 143 | } 144 | } 145 | 146 | func TestDecimal_Add2(t *testing.T) { 147 | a := New(1234, 3) 148 | b := New(9876, -4) 149 | 150 | c := a.Add(b) 151 | if c.String() != "1234" { 152 | t.Errorf("error") 153 | } 154 | } 155 | 156 | func TestDecimal_Sub1(t *testing.T) { 157 | a := New(1234, -4) 158 | b := New(9876, 3) 159 | 160 | c := a.Sub(b) 161 | if c.String() != "-98759998766" { 162 | t.Errorf(c.String()) 163 | } 164 | } 165 | 166 | func TestDecimal_Sub2(t *testing.T) { 167 | a := New(1234, 3) 168 | b := New(9876, -4) 169 | 170 | c := a.Sub(b) 171 | if c.String() != "1233" { 172 | t.Errorf(c.String()) 173 | } 174 | } 175 | 176 | func TestDecimal_Mul(t *testing.T) { 177 | a := New(1398699, -4) 178 | b := New(6, -3) 179 | 180 | c := a.Mul(b) 181 | if c.String() != "8392" { 182 | t.Errorf(c.String()) 183 | } 184 | } 185 | 186 | func TestDecimal_Div1(t *testing.T) { 187 | a := New(1398699, -4) 188 | b := New(1006, -3) 189 | 190 | c := a.Div(b) 191 | if c.String() != "1390356" { 192 | t.Errorf(c.String()) 193 | } 194 | } 195 | 196 | func TestDecimal_Div2(t *testing.T) { 197 | a := New(2345, -3) 198 | b := New(2, 0) 199 | 200 | c := a.Div(b) 201 | if c.String() != "1172" { 202 | t.Errorf(c.String()) 203 | } 204 | } 205 | 206 | func TestDecimal_Div3(t *testing.T) { 207 | a := New(18275499, -6) 208 | b := New(16275499, -6) 209 | 210 | c := a.Div(b) 211 | if c.String() != "1122884" { 212 | t.Errorf(c.String()) 213 | } 214 | } 215 | func TestDecimal_Cmp1(t *testing.T) { 216 | a := New(123, 3) 217 | b := New(-1234, 2) 218 | 219 | if a.Cmp(b) != 1 { 220 | t.Errorf("Error") 221 | } 222 | } 223 | 224 | func TestDecimal_Cmp2(t *testing.T) { 225 | a := New(123, 3) 226 | b := New(1234, 2) 227 | 228 | if a.Cmp(b) != -1 { 229 | t.Errorf("Error") 230 | } 231 | } 232 | 233 | func TestDecimal_StringScaled(t *testing.T) { 234 | a := New(123, 3) 235 | if a.StringScaled(-2) != "12300000" { 236 | t.Errorf("Error") 237 | } 238 | } 239 | 240 | func TestDecimal_StringScaled2(t *testing.T) { 241 | a := New(1234, -2) 242 | if a.StringScaled(0) != "12" { 243 | t.Errorf("Error") 244 | } 245 | } 246 | 247 | func TestDecimal_FormattedString(t *testing.T) { 248 | a := New(1234, -2) 249 | if a.FormattedString() != "12.34" { 250 | t.Errorf(a.FormattedString()) 251 | } 252 | } 253 | 254 | func TestDecimal_FormattedString1(t *testing.T) { 255 | a := New(1234, 2) 256 | if a.FormattedString() != "123400" { 257 | t.Errorf(a.FormattedString()) 258 | } 259 | } 260 | 261 | func TestDecimal_FormattedString2(t *testing.T) { 262 | a := New(-1234, 2) 263 | if a.FormattedString() != "-123400" { 264 | t.Errorf(a.FormattedString()) 265 | } 266 | } 267 | 268 | func TestDecimal_FormattedString3(t *testing.T) { 269 | a := New(1234, -6) 270 | if a.FormattedString() != "0.001234" { 271 | t.Errorf(a.FormattedString()) 272 | } 273 | } 274 | 275 | func TestDecimal_FormattedString4(t *testing.T) { 276 | a := New(1000, -6) 277 | if a.FormattedString() != "0.001000" { 278 | t.Errorf(a.FormattedString()) 279 | } 280 | } 281 | 282 | func TestDecimal_FormattedString5(t *testing.T) { 283 | a := New(-1234, -2) 284 | if a.FormattedString() != "-12.34" { 285 | t.Errorf(a.FormattedString()) 286 | } 287 | } 288 | 289 | func TestDecimal_FormattedString6(t *testing.T) { 290 | a := NewFromFloat(0.12, -4) 291 | if a.FormattedString() != "0.1200" { 292 | t.Errorf(a.FormattedString()) 293 | } 294 | } 295 | 296 | func TestDecimal_FormattedString7(t *testing.T) { 297 | a := NewFromFloat(-0.12, -4) 298 | if a.FormattedString() != "-0.1200" { 299 | t.Errorf(a.FormattedString()) 300 | } 301 | } 302 | --------------------------------------------------------------------------------