-
Notifications
You must be signed in to change notification settings - Fork 46
/
fraction.go
140 lines (127 loc) · 3.16 KB
/
fraction.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
package weave
import (
"encoding/json"
"fmt"
"regexp"
"strconv"
"strings"
"github.com/iov-one/weave/errors"
)
// String returns a human readable fraction representation.
func (f *Fraction) String() string {
if f == nil {
return "nil"
}
if f.Numerator == 0 {
return "0"
}
if f.Denominator == 1 {
return fmt.Sprint(f.Numerator)
}
return fmt.Sprintf("%d/%d", f.Numerator, f.Denominator)
}
func (f Fraction) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Numerator uint32 `json:"numerator"`
Denominator uint32 `json:"denominator"`
}{
Numerator: f.Numerator,
Denominator: f.Denominator,
})
}
func (f *Fraction) UnmarshalJSON(raw []byte) error {
// Prioritize human readable format.
var human string
if err := json.Unmarshal(raw, &human); err == nil {
if frac, err := ParseFractionString(human); err != nil {
return errors.Wrap(err, "fraction string")
} else {
*f = *frac
return nil
}
}
var frac struct {
Numerator uint32
Denominator uint32
}
if err := json.Unmarshal(raw, &frac); err != nil {
return err
}
f.Numerator = frac.Numerator
f.Denominator = frac.Denominator
return nil
}
// Validate returns an error if this fraction represents an invalid value.
func (f Fraction) Validate() error {
if f.Denominator == 0 && f.Numerator != 0 {
return errors.Wrap(errors.ErrState, "zero division")
}
return nil
}
// Normalize returns a new fraction instance that has its numerator and
// denominator reduced to the smallest possible representation.
func (f Fraction) Normalize() Fraction {
div := uintGcd(f.Numerator, f.Denominator)
return Fraction{
Numerator: f.Numerator / div,
Denominator: f.Denominator / div,
}
}
func uintGcd(a, b uint32) uint32 {
for b != 0 {
t := b
b = a % b
a = t
}
return a
}
// Compare returns an integer comparing two fraction numbers. The result will be
// 0 if a == b,
// -1 if a < b,
// +1 if a > b
// Comparison of invalid fraction values return undefined value.
func (a Fraction) Compare(b Fraction) int {
if a.Numerator == 0 {
if b.Numerator == 0 {
return 0
}
return -1
}
if b.Numerator == 0 {
return 1
}
aNum := a.Numerator * b.Denominator
bNum := b.Numerator * a.Denominator
switch {
case aNum == bNum:
return 0
case aNum < bNum:
return -1
case aNum > bNum:
return 1
default:
panic("dead code")
}
}
// ParseFractionString returns a fraction value that is represented by given
// string. This function fails if given string does not represent a fraction
// value.
// This fuction does not fail if representation format is correct but the value
// is invalid (i.e. value of "2/0").
func ParseFractionString(raw string) (*Fraction, error) {
raw = whitespaceRx.ReplaceAllString(raw, "")
chunks := strings.SplitN(raw, "/", 2)
n, err := strconv.ParseUint(chunks[0], 10, 32)
if err != nil {
return nil, errors.Wrap(err, "numerator")
}
if len(chunks) == 1 {
return &Fraction{Numerator: uint32(n), Denominator: 1}, nil
}
d, err := strconv.ParseUint(chunks[1], 10, 32)
if err != nil {
return nil, errors.Wrap(err, "denominator")
}
return &Fraction{Numerator: uint32(n), Denominator: uint32(d)}, nil
}
var whitespaceRx = regexp.MustCompile("[ \t]")