rounding methods and decimals for interval

This commit is contained in:
Radu Ioan Fericean
2013-07-14 17:59:14 +03:00
parent 3123b44501
commit ae3a6a786e
7 changed files with 140 additions and 16 deletions

View File

@@ -49,7 +49,7 @@ func TestApStoreRestoreJson(t *testing.T) {
ap := &ActivationPeriod{ActivationTime: d}
ap.AddInterval(i)
result, _ := json.Marshal(ap)
expected := "{\"ActivationTime\":\"2012-02-01T14:30:01Z\",\"Intervals\":[{\"Years\":null,\"Months\":[2],\"MonthDays\":[1],\"WeekDays\":[3,4],\"StartTime\":\"14:30:00\",\"EndTime\":\"15:00:00\",\"Weight\":0,\"ConnectFee\":0,\"Price\":0,\"PricedUnits\":0,\"RateIncrements\":0}]}"
expected := "{\"ActivationTime\":\"2012-02-01T14:30:01Z\",\"Intervals\":[{\"Years\":null,\"Months\":[2],\"MonthDays\":[1],\"WeekDays\":[3,4],\"StartTime\":\"14:30:00\",\"EndTime\":\"15:00:00\",\"Weight\":0,\"ConnectFee\":0,\"Price\":0,\"PricedUnits\":0,\"RateIncrements\":0,\"RoundingMethod\":\"\",\"RoundingDecimals\":0}]}"
if string(result) != expected {
t.Errorf("Expected %q was %q", expected, result)
}
@@ -66,7 +66,7 @@ func TestApStoreRestoreBlank(t *testing.T) {
ap := &ActivationPeriod{ActivationTime: d}
ap.AddInterval(i)
result, _ := json.Marshal(ap)
expected := "{\"ActivationTime\":\"2012-02-01T14:30:01Z\",\"Intervals\":[{\"Years\":null,\"Months\":null,\"MonthDays\":null,\"WeekDays\":null,\"StartTime\":\"\",\"EndTime\":\"\",\"Weight\":0,\"ConnectFee\":0,\"Price\":0,\"PricedUnits\":0,\"RateIncrements\":0}]}"
expected := "{\"ActivationTime\":\"2012-02-01T14:30:01Z\",\"Intervals\":[{\"Years\":null,\"Months\":null,\"MonthDays\":null,\"WeekDays\":null,\"StartTime\":\"\",\"EndTime\":\"\",\"Weight\":0,\"ConnectFee\":0,\"Price\":0,\"PricedUnits\":0,\"RateIncrements\":0,\"RoundingMethod\":\"\",\"RoundingDecimals\":0}]}"
if string(result) != expected {
t.Errorf("Expected %q was %q", expected, result)
}

View File

@@ -20,6 +20,8 @@ package rater
import (
"fmt"
"github.com/cgrates/cgrates/utils"
"math"
"reflect"
"strconv"
"strings"
@@ -37,6 +39,8 @@ type Interval struct {
WeekDays WeekDays
StartTime, EndTime string // ##:##:## format
Weight, ConnectFee, Price, PricedUnits, RateIncrements float64
RoundingMethod string
RoundingDecimals int
}
/*
@@ -132,3 +136,13 @@ func (i *Interval) Equal(o *Interval) bool {
i.StartTime == o.StartTime &&
i.EndTime == o.EndTime
}
func (i *Interval) GetCost(duration float64) (cost float64) {
if i.PricedUnits != 0 {
cost = math.Ceil(duration/i.RateIncrements) * i.RateIncrements * (i.Price / i.PricedUnits)
} else {
cost = math.Ceil(duration/i.RateIncrements) * i.RateIncrements * i.Price
}
return utils.Round(cost, i.RoundingDecimals, i.RoundingMethod)
}

View File

@@ -424,13 +424,15 @@ func (i *Interval) Store() (result string, err error) {
result += strconv.FormatFloat(i.ConnectFee, 'f', -1, 64) + ";"
result += strconv.FormatFloat(i.Price, 'f', -1, 64) + ";"
result += strconv.FormatFloat(i.PricedUnits, 'f', -1, 64) + ";"
result += strconv.FormatFloat(i.RateIncrements, 'f', -1, 64)
result += strconv.FormatFloat(i.RateIncrements, 'f', -1, 64) + ";"
result += i.RoundingMethod + ";"
result += strconv.Itoa(i.RoundingDecimals)
return
}
func (i *Interval) Restore(input string) error {
is := strings.Split(input, ";")
if len(is) != 11 {
if len(is) != 13 {
return notEnoughElements
}
if err := i.Years.Restore(is[0]); err != nil {
@@ -452,6 +454,8 @@ func (i *Interval) Restore(input string) error {
i.Price, _ = strconv.ParseFloat(is[8], 64)
i.PricedUnits, _ = strconv.ParseFloat(is[9], 64)
i.RateIncrements, _ = strconv.ParseFloat(is[10], 64)
i.RoundingMethod = is[11]
i.RoundingDecimals, _ = strconv.Atoi(is[12])
return nil
}

View File

@@ -35,7 +35,7 @@ func TestSimpleMarshallerApStoreRestore(t *testing.T) {
ap := &ActivationPeriod{ActivationTime: d}
ap.AddInterval(i)
result, err := ap.Store()
expected := "1328106601000000000|;2;1;3,4;14:30:00;15:00:00;0;0;0;0;0"
expected := "1328106601000000000|;2;1;3,4;14:30:00;15:00:00;0;0;0;0;0;;0"
if err != nil || !reflect.DeepEqual(result, expected) {
t.Errorf("Expected %q was %q", expected, result)
}
@@ -47,7 +47,7 @@ func TestSimpleMarshallerApStoreRestore(t *testing.T) {
}
func TestSimpleMarshallerApRestoreFromString(t *testing.T) {
s := "1325376000000000000|;1,2,3,4,5,6,7,8,9,10,11,12;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;1,2,3,4,5,6,0;00:00:00;;10;0;0.2;60;1\n"
s := "1325376000000000000|;1,2,3,4,5,6,7,8,9,10,11,12;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;1,2,3,4,5,6,0;00:00:00;;10;0;0.2;60;1;;0\n"
ap := &ActivationPeriod{}
err := ap.Restore(s)
if err != nil || len(ap.Intervals) != 1 {
@@ -68,7 +68,7 @@ func TestRpStoreRestore(t *testing.T) {
rp := &RatingProfile{FallbackKey: "test"}
rp.AddActivationPeriodIfNotPresent("0723", ap)
result, err := rp.Store()
expected := "test>0723=1328106601000000000|;2;1;3,4;14:30:00;15:00:00;0;0;0;0;0"
expected := "test>0723=1328106601000000000|;2;1;3,4;14:30:00;15:00:00;0;0;0;0;0;;0"
if err != nil || !reflect.DeepEqual(result, expected) {
t.Errorf("Expected %q was %q", expected, result)
}
@@ -101,7 +101,7 @@ func TestActionTimingStoreRestore(t *testing.T) {
ActionsId: "Commando",
}
r, err := at.Store()
if err != nil || r != "some uuid|test|one,two,three|;1,2,3,4,5,6,7,8,9,10,11,12;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;1,2,3,4,5;18:00:00;00:00:00;10;0;1;60;1|10|Commando" {
if err != nil || r != "some uuid|test|one,two,three|;1,2,3,4,5,6,7,8,9,10,11,12;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;1,2,3,4,5;18:00:00;00:00:00;10;0;1;60;1;;0|10|Commando" {
t.Errorf("Error serializing action timing: %v", string(r))
}
o := &ActionTiming{}
@@ -146,7 +146,7 @@ func TestIntervalStoreRestore(t *testing.T) {
RateIncrements: 1,
}
r, err := i.Store()
if err != nil || r != ";1,2,3,4,5,6,7,8,9,10,11,12;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;1,2,3,4,5;18:00:00;00:00:00;10;0;1;60;1" {
if err != nil || r != ";1,2,3,4,5,6,7,8,9,10,11,12;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;1,2,3,4,5;18:00:00;00:00:00;10;0;1;60;1;;0" {
t.Errorf("Error serializing interval: %v", string(r))
}
o := &Interval{}
@@ -157,7 +157,7 @@ func TestIntervalStoreRestore(t *testing.T) {
}
func TestIntervalRestoreFromString(t *testing.T) {
s := ";1,2,3,4,5,6,7,8,9,10,11,12;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;1,2,3,4,5,6,0;00:00:00;;10;0;0.2;60;1"
s := ";1,2,3,4,5,6,7,8,9,10,11,12;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;1,2,3,4,5,6,0;00:00:00;;10;0;0.2;60;0;;1"
i := Interval{}
err := i.Restore(s)
if err != nil || i.Price != 0.2 {

View File

@@ -20,7 +20,6 @@ package rater
import (
"fmt"
"math"
"time"
)
@@ -64,11 +63,7 @@ func (ts *TimeSpan) getCost(cd *CallDescriptor) (cost float64) {
if i.RateIncrements == 0 {
i.RateIncrements = 1
}
if i.PricedUnits != 0 {
cost = math.Ceil(duration/i.RateIncrements) * i.RateIncrements * (i.Price / i.PricedUnits)
} else {
cost = math.Ceil(duration/i.RateIncrements) * i.RateIncrements * i.Price
}
cost = i.GetCost(duration)
// if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil {
// userBalance.mux.RLock()
// if percentageDiscount, err := userBalance.getVolumeDiscount(cd.Destination, INBOUND); err == nil && percentageDiscount > 0 {

View File

@@ -23,6 +23,7 @@ import (
"crypto/sha1"
"encoding/hex"
"fmt"
"math"
"strconv"
"time"
)
@@ -63,3 +64,33 @@ func GenUUID() string {
return hex.EncodeToString(uuid)
}
// Round return rounded version of x with prec precision.
//
// Special cases are:
// Round(±0) = ±0
// Round(±Inf) = ±Inf
// Round(NaN) = NaN
func Round(x float64, prec int, method string) float64 {
var rounder float64
pow := math.Pow(10, float64(prec))
intermed := x * pow
_, frac := math.Modf(intermed)
switch method {
case "*up":
rounder = math.Ceil(intermed)
case "*down":
rounder = math.Floor(intermed)
case "*middle":
if frac >= 0.5 {
rounder = math.Ceil(intermed)
} else {
rounder = math.Floor(intermed)
}
default:
rounder = intermed
}
return rounder / pow
}

View File

@@ -39,3 +39,83 @@ func TestUUID(t *testing.T) {
t.Fatalf("GenUUID error %s", uuid)
}
}
func TestRoundUp(t *testing.T) {
result := Round(12.52, 0, "*middle")
expected := 13.0
if result != expected {
t.Errorf("Error rounding up: sould be %v was %v", expected, result)
}
}
func TestRoundUpMiddle(t *testing.T) {
result := Round(12.5, 0, "*middle")
expected := 13.0
if result != expected {
t.Errorf("Error rounding up: sould be %v was %v", expected, result)
}
}
func TestRoundDown(t *testing.T) {
result := Round(12.49, 0, "*middle")
expected := 12.0
if result != expected {
t.Errorf("Error rounding up: sould be %v was %v", expected, result)
}
}
func TestRoundPrec(t *testing.T) {
result := Round(12.49, 1, "*middle")
expected := 12.5
if result != expected {
t.Errorf("Error rounding up: sould be %v was %v", expected, result)
}
}
func TestRoundPrecNothing(t *testing.T) {
result := Round(12.49, 2, "*middle")
expected := 12.49
if result != expected {
t.Errorf("Error rounding up: sould be %v was %v", expected, result)
}
}
func TestRoundPrecNoTouch(t *testing.T) {
result := Round(12.49, 2, "")
expected := 12.49
if result != expected {
t.Errorf("Error rounding up: sould be %v was %v", expected, result)
}
}
func TestRoundByMethodUp1(t *testing.T) {
result := Round(12.49, 1, "*up")
expected := 12.5
if result != expected {
t.Errorf("Error rounding up: sould be %v was %v", expected, result)
}
}
func TestRoundByMethodUp2(t *testing.T) {
result := Round(12.21, 1, "*up")
expected := 12.3
if result != expected {
t.Errorf("Error rounding up: sould be %v was %v", expected, result)
}
}
func TestRoundByMethodDown1(t *testing.T) {
result := Round(12.49, 1, "*down")
expected := 12.4
if result != expected {
t.Errorf("Error rounding up: sould be %v was %v", expected, result)
}
}
func TestRoundByMethodDown2(t *testing.T) {
result := Round(12.21, 1, "*down")
expected := 12.2
if result != expected {
t.Errorf("Error rounding up: sould be %v was %v", expected, result)
}
}