-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
24 changed files
with
5,350 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,3 +20,5 @@ | |
# Go workspace file | ||
go.work | ||
go.work.sum | ||
|
||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package ucum | ||
|
||
import ( | ||
"fmt" | ||
"github.com/iimos/ucum/internal/data" | ||
"github.com/iimos/ucum/internal/types" | ||
"math/big" | ||
) | ||
|
||
var ( | ||
bigZero = big.NewInt(0) | ||
bigRatOne = big.NewRat(1, 1) | ||
) | ||
|
||
// PairConverter makes conversion between two UCUM units. | ||
type PairConverter interface { | ||
ConvRat(val *big.Rat) *big.Rat | ||
ConvBigInt(val *big.Int) (converted *big.Int, exact bool) | ||
ConvFloat64(val float64) float64 | ||
} | ||
|
||
// NewPairConverter creates a new PairConverter. | ||
func NewPairConverter(from, to Unit) (PairConverter, error) { | ||
a := Normalize(from).u | ||
b := Normalize(to).u | ||
|
||
if len(a.Components) != len(b.Components) { | ||
// Special units are not normalizable so if number of components doesn't match it might be that units are special | ||
specConv, ok := newSpecialConverter(a, b) | ||
if !ok { | ||
return nil, fmt.Errorf("ucum: %q cannot be converted to %q", from.String(), to.String()) | ||
} | ||
return specConv, nil | ||
} | ||
|
||
for key, expA := range a.Components { | ||
expB, exists := b.Components[key] // normalized units are stripped from annotations, so we can look up directly by key | ||
if !exists { | ||
// Special units are not normalizable so try to interpret it as a special units if mismatched. | ||
specConv, ok := newSpecialConverter(a, b) | ||
if !ok { | ||
return nil, fmt.Errorf("ucum: %q cannot be converted to %q", from.String(), to.String()) | ||
} | ||
return specConv, nil | ||
} | ||
if expA != expB { | ||
return nil, fmt.Errorf("ucum: %q cannot be converted to %q", from.String(), to.String()) | ||
} | ||
} | ||
ratio := new(big.Rat).Quo(a.Coeff, b.Coeff) | ||
ratioFloat, _ := ratio.Float64() | ||
return &linearConverter{ | ||
from: from, | ||
to: to, | ||
ratio: *ratio, | ||
ratioFloat: ratioFloat, | ||
}, nil | ||
} | ||
|
||
// newSpecialConverter creates convertor for special units. | ||
// It assumes that the special units are already normalized. | ||
func newSpecialConverter(from, to types.Unit) (PairConverter, bool) { | ||
specialConv := func(u types.Unit) (conv data.SpecialUnitConv, ok bool) { | ||
if len(u.Components) != 1 { | ||
return data.SpecialUnitConv{}, false | ||
} | ||
for key, exp := range u.Components { | ||
if exp != 1 { | ||
// special units cannot be raised to a power | ||
return data.SpecialUnitConv{}, false | ||
} | ||
conv, ok = data.SpecialUnits[key.AtomCode] | ||
return conv, ok | ||
} | ||
return data.SpecialUnitConv{}, false | ||
} | ||
|
||
if fromConv, ok := specialConv(from); ok { | ||
interm := MustParse([]byte(fromConv.Unit)) | ||
toConv, err := NewPairConverter(interm, Unit{u: to}) | ||
if err != nil { | ||
return nil, false | ||
} | ||
return &specialConverter{ | ||
multiplyBefore: from.Coeff, | ||
from: fromConv, | ||
to: toConv, | ||
divideAfter: bigRatOne, | ||
}, true | ||
} | ||
|
||
if toConv, ok := specialConv(to); ok { | ||
interm := MustParse([]byte(toConv.Unit)) | ||
fromConv, err := NewPairConverter(Unit{u: from}, interm) | ||
if err != nil { | ||
return nil, false | ||
} | ||
return &specialConverter{ | ||
multiplyBefore: bigRatOne, | ||
from: fromConv, | ||
to: toConv.Invert(), | ||
divideAfter: to.Coeff, | ||
}, true | ||
} | ||
|
||
return nil, false | ||
} | ||
|
||
func ConvBigInt(from, to Unit, val *big.Int) (result *big.Int, exact bool, err error) { | ||
if from.u.Orig != "" && from.u.Orig == to.u.Orig { | ||
return (&big.Int{}).Set(val), true, nil | ||
} | ||
converter, err := NewPairConverter(from, to) | ||
if err != nil { | ||
return nil, false, err | ||
} | ||
result, exact = converter.ConvBigInt(val) | ||
return result, exact, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package ucum | ||
|
||
import "math/big" | ||
|
||
type linearConverter struct { | ||
from, to Unit | ||
ratio big.Rat | ||
ratioFloat float64 | ||
} | ||
|
||
func (c *linearConverter) ConvRat(val *big.Rat) *big.Rat { | ||
return new(big.Rat).Mul(&c.ratio, val) | ||
} | ||
|
||
func (c *linearConverter) ConvBigInt(val *big.Int) (converted *big.Int, exact bool) { | ||
ret := new(big.Int).Mul(val, c.ratio.Num()) | ||
if c.ratio.IsInt() { | ||
return ret, true | ||
} | ||
_, rem := ret.QuoRem(val, c.ratio.Denom(), new(big.Int)) | ||
return ret, rem.Cmp(bigZero) == 0 | ||
} | ||
|
||
func (c *linearConverter) ConvFloat64(val float64) float64 { | ||
return c.ratioFloat * val | ||
} | ||
|
||
func (c *linearConverter) String() string { | ||
return "ucum.PairConverter(" + c.from.String() + "->" + c.to.String() + ")" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package ucum | ||
|
||
import ( | ||
"math/big" | ||
) | ||
|
||
// specialConverter is a converter for special units. | ||
// Convertation algorithm: y = to(from(multiplyBefore * x)) / divideAfter | ||
type specialConverter struct { | ||
from PairConverter // converts from source unit to intermediate one | ||
to PairConverter // converts from intermediate unit to target one | ||
multiplyBefore *big.Rat | ||
divideAfter *big.Rat | ||
} | ||
|
||
var _ PairConverter = (*specialConverter)(nil) | ||
|
||
func (c *specialConverter) ConvRat(val *big.Rat) *big.Rat { | ||
v1 := new(big.Rat).Mul(val, c.multiplyBefore) | ||
v2 := c.from.ConvRat(v1) | ||
v3 := c.to.ConvRat(v2) | ||
v4 := v3.Quo(v3, c.divideAfter) | ||
return v4 | ||
} | ||
|
||
func (c *specialConverter) ConvBigInt(val *big.Int) (converted *big.Int, exact bool) { | ||
rat := new(big.Rat).SetInt(val) | ||
result := c.ConvRat(rat) | ||
quo, rem := new(big.Int).QuoRem(result.Num(), result.Denom(), new(big.Int)) | ||
return quo, rem.Cmp(bigZero) == 0 | ||
} | ||
|
||
func (c *specialConverter) ConvFloat64(val float64) float64 { | ||
multiplyBefore, _ := c.multiplyBefore.Float64() | ||
divideAfter, _ := c.divideAfter.Float64() | ||
v1 := val * multiplyBefore | ||
v2 := c.from.ConvFloat64(v1) | ||
v3 := c.to.ConvFloat64(v2) | ||
v4 := v3 / divideAfter | ||
return v4 | ||
} |
Oops, something went wrong.