Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
iimos committed Jul 14, 2024
1 parent 18b67b2 commit 3c3b456
Show file tree
Hide file tree
Showing 24 changed files with 5,350 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@
# Go workspace file
go.work
go.work.sum

.idea
119 changes: 119 additions & 0 deletions conv.go
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
}
30 changes: 30 additions & 0 deletions conv_linear.go
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() + ")"
}
41 changes: 41 additions & 0 deletions conv_special.go
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
}
Loading

0 comments on commit 3c3b456

Please sign in to comment.