-
Notifications
You must be signed in to change notification settings - Fork 31
/
Copy pathfixed.go
462 lines (398 loc) · 9.11 KB
/
fixed.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
package fixed
// release under the terms of file license.txt
import (
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"strconv"
"strings"
)
// Fixed is a fixed precision 38.24 number (supports 11.7 digits). It supports NaN.
type Fixed struct {
fp int64
}
// the following constants can be changed to configure a different number of decimal places - these are
// the only required changes. only 18 significant digits are supported due to NaN
const nPlaces = 7
const scale = int64(10 * 10 * 10 * 10 * 10 * 10 * 10)
const zeros = "0000000"
const MAX = float64(99999999999.9999999)
const nan = int64(1<<63 - 1)
var NaN = Fixed{fp: nan}
var ZERO = Fixed{fp: 0}
var errTooLarge = errors.New("significand too large")
var errFormat = errors.New("invalid encoding")
// NewS creates a new Fixed from a string, returning NaN if the string could not be parsed
func NewS(s string) Fixed {
f, _ := NewSErr(s)
return f
}
// NewSErr creates a new Fixed from a string, returning NaN, and error if the string could not be parsed
func NewSErr(s string) (Fixed, error) {
if strings.ContainsAny(s, "eE") {
f, err := strconv.ParseFloat(s, 64)
if err != nil {
return NaN, err
}
return NewF(f), nil
}
if "NaN" == s {
return NaN, nil
}
period := strings.Index(s, ".")
var i int64
var f int64
var sign int64 = 1
var err error
if period == -1 {
i, err = strconv.ParseInt(s, 10, 64)
if err != nil {
return NaN, errors.New("cannot parse")
}
if i < 0 {
sign = -1
i = i * -1
}
} else {
if len(s[:period]) > 0 {
i, err = strconv.ParseInt(s[:period], 10, 64)
if err != nil {
return NaN, errors.New("cannot parse")
}
if i < 0 || s[0] == '-' {
sign = -1
i = i * -1
}
}
fs := s[period+1:]
fs = fs + zeros[:max(0, nPlaces-len(fs))]
f, err = strconv.ParseInt(fs[0:nPlaces], 10, 64)
if err != nil {
return NaN, errors.New("cannot parse")
}
}
if float64(i) > MAX {
return NaN, errTooLarge
}
return Fixed{fp: sign * (i*scale + f)}, nil
}
// Parse creates a new Fixed from a string, returning NaN, and error if the string could not be parsed. Same as NewSErr
// but more standard naming
func Parse(s string) (Fixed, error) {
return NewSErr(s)
}
// MustParse creates a new Fixed from a string, and panics if the string could not be parsed
func MustParse(s string) Fixed {
f, err := NewSErr(s)
if err != nil {
panic(err)
}
return f
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
// NewF creates a Fixed from an float64, rounding at the 8th decimal place
func NewF(f float64) Fixed {
if math.IsNaN(f) {
return Fixed{fp: nan}
}
if f >= MAX || f <= -MAX {
return NaN
}
round := .5
if f < 0 {
round = -0.5
}
return Fixed{fp: int64(f*float64(scale) + round)}
}
// NewI creates a Fixed for an integer, moving the decimal point n places to the left
// For example, NewI(123,1) becomes 12.3. If n > 7, the value is truncated
func NewI(i int64, n uint) Fixed {
if n > nPlaces {
i = i / int64(math.Pow10(int(n-nPlaces)))
n = nPlaces
}
i = i * int64(math.Pow10(int(nPlaces-n)))
return Fixed{fp: i}
}
func (f Fixed) IsNaN() bool {
return f.fp == nan
}
func (f Fixed) IsZero() bool {
return f.Equal(ZERO)
}
// Sign returns:
//
// -1 if f < 0
// 0 if f == 0 or NaN
// +1 if f > 0
func (f Fixed) Sign() int {
if f.IsNaN() {
return 0
}
return f.Cmp(ZERO)
}
// Float converts the Fixed to a float64
func (f Fixed) Float() float64 {
if f.IsNaN() {
return math.NaN()
}
return float64(f.fp) / float64(scale)
}
// Add adds f0 to f producing a Fixed. If either operand is NaN, NaN is returned
func (f Fixed) Add(f0 Fixed) Fixed {
if f.IsNaN() || f0.IsNaN() {
return NaN
}
return Fixed{fp: f.fp + f0.fp}
}
// Sub subtracts f0 from f producing a Fixed. If either operand is NaN, NaN is returned
func (f Fixed) Sub(f0 Fixed) Fixed {
if f.IsNaN() || f0.IsNaN() {
return NaN
}
return Fixed{fp: f.fp - f0.fp}
}
// Abs returns the absolute value of f. If f is NaN, NaN is returned
func (f Fixed) Abs() Fixed {
if f.IsNaN() {
return NaN
}
if f.Sign() >= 0 {
return f
}
f0 := Fixed{fp: f.fp * -1}
return f0
}
func abs(i int64) int64 {
if i >= 0 {
return i
}
return i * -1
}
// Mul multiplies f by f0 returning a Fixed. If either operand is NaN, NaN is returned
func (f Fixed) Mul(f0 Fixed) Fixed {
if f.IsNaN() || f0.IsNaN() {
return NaN
}
fp_a := f.fp / scale
fp_b := f.fp % scale
fp0_a := f0.fp / scale
fp0_b := f0.fp % scale
var _sign = int64(f.Sign() * f0.Sign())
var result int64
if fp0_a != 0 {
result = fp_a*fp0_a*scale + fp_b*fp0_a
}
if fp0_b != 0 {
result = result + (fp_a * fp0_b) + ((fp_b)*fp0_b+5*_sign*(scale/10))/scale
}
return Fixed{fp: result}
}
// Div divides f by f0 returning a Fixed. If either operand is NaN, NaN is returned
func (f Fixed) Div(f0 Fixed) Fixed {
if f.IsNaN() || f0.IsNaN() {
return NaN
}
return NewF(f.Float() / f0.Float())
}
func sign(fp int64) int64 {
if fp < 0 {
return -1
}
return 1
}
// Round returns a rounded (half-up, away from zero) to n decimal places
func (f Fixed) Round(n int) Fixed {
if f.IsNaN() {
return NaN
}
fraction := f.fp % scale
f0 := fraction / int64(math.Pow10(nPlaces-n-1))
digit := abs(f0 % 10)
f0 = (f0 / 10)
if digit >= 5 {
f0 += 1 * sign(f.fp)
}
f0 = f0 * int64(math.Pow10(nPlaces-n))
intpart := f.fp - fraction
fp := intpart + f0
return Fixed{fp: fp}
}
// Equal returns true if the f == f0. If either operand is NaN, false is returned. Use IsNaN() to test for NaN
func (f Fixed) Equal(f0 Fixed) bool {
if f.IsNaN() || f0.IsNaN() {
return false
}
return f.Cmp(f0) == 0
}
// GreaterThan tests Cmp() for 1
func (f Fixed) GreaterThan(f0 Fixed) bool {
return f.Cmp(f0) == 1
}
// GreaterThaOrEqual tests Cmp() for 1 or 0
func (f Fixed) GreaterThanOrEqual(f0 Fixed) bool {
cmp := f.Cmp(f0)
return cmp == 1 || cmp == 0
}
// LessThan tests Cmp() for -1
func (f Fixed) LessThan(f0 Fixed) bool {
return f.Cmp(f0) == -1
}
// LessThan tests Cmp() for -1 or 0
func (f Fixed) LessThanOrEqual(f0 Fixed) bool {
cmp := f.Cmp(f0)
return cmp == -1 || cmp == 0
}
// Cmp compares two Fixed. If f == f0, return 0. If f > f0, return 1. If f < f0, return -1. If both are NaN, return 0. If f is NaN, return 1. If f0 is NaN, return -1
func (f Fixed) Cmp(f0 Fixed) int {
if f.IsNaN() && f0.IsNaN() {
return 0
}
if f.IsNaN() {
return 1
}
if f0.IsNaN() {
return -1
}
if f.fp == f0.fp {
return 0
}
if f.fp < f0.fp {
return -1
}
return 1
}
// String converts a Fixed to a string, dropping trailing zeros
func (f Fixed) String() string {
s, point := f.tostr()
if point == -1 {
return s
}
index := len(s) - 1
for ; index != point; index-- {
if s[index] != '0' {
return s[:index+1]
}
}
return s[:point]
}
// StringN converts a Fixed to a String with a specified number of decimal places, truncating as required
func (f Fixed) StringN(decimals int) string {
s, point := f.tostr()
if point == -1 {
return s
}
if decimals == 0 {
return s[:point]
} else {
return s[:point+decimals+1]
}
}
func (f Fixed) tostr() (string, int) {
fp := f.fp
if fp == 0 {
return "0." + zeros, 1
}
if fp == nan {
return "NaN", -1
}
b := make([]byte, 24)
b = itoa(b, fp)
return string(b), len(b) - nPlaces - 1
}
func itoa(buf []byte, val int64) []byte {
neg := val < 0
if neg {
val = val * -1
}
i := len(buf) - 1
idec := i - nPlaces
for val >= 10 || i >= idec {
buf[i] = byte(val%10 + '0')
i--
if i == idec {
buf[i] = '.'
i--
}
val /= 10
}
buf[i] = byte(val + '0')
if neg {
i--
buf[i] = '-'
}
return buf[i:]
}
// Int return the integer portion of the Fixed, or 0 if NaN
func (f Fixed) Int() int64 {
if f.IsNaN() {
return 0
}
return f.fp / scale
}
// Frac return the fractional portion of the Fixed, or NaN if NaN
func (f Fixed) Frac() float64 {
if f.IsNaN() {
return math.NaN()
}
return float64(f.fp%scale) / float64(scale)
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
func (f *Fixed) UnmarshalBinary(data []byte) error {
fp, n := binary.Varint(data)
if n < 0 {
return errFormat
}
f.fp = fp
return nil
}
// MarshalBinary implements the encoding.BinaryMarshaler interface.
func (f Fixed) MarshalBinary() (data []byte, err error) {
var buffer [binary.MaxVarintLen64]byte
n := binary.PutVarint(buffer[:], f.fp)
return buffer[:n], nil
}
// WriteTo write the Fixed to an io.Writer, returning the number of bytes written
func (f Fixed) WriteTo(w io.ByteWriter) error {
return writeVarint(w, f.fp)
}
// ReadFrom reads a Fixed from an io.Reader
func ReadFrom(r io.ByteReader) (Fixed, error) {
fp, err := binary.ReadVarint(r)
if err != nil {
return NaN, err
}
return Fixed{fp: fp}, nil
}
// UnmarshalJSON implements the json.Unmarshaler interface.
func (f *Fixed) UnmarshalJSON(bytes []byte) error {
s := string(bytes)
if s == "null" {
return nil
}
if s == "\"NaN\"" {
*f = NaN
return nil
}
fixed, err := NewSErr(s)
*f = fixed
if err != nil {
return fmt.Errorf("Error decoding string '%s': %s", s, err)
}
return nil
}
// MarshalJSON implements the json.Marshaler interface.
func (f Fixed) MarshalJSON() ([]byte, error) {
if f.IsNaN() {
return []byte("\"NaN\""), nil
}
buffer := make([]byte, 24)
return itoa(buffer, f.fp), nil
}