Replace Fixed14_4Float with a integer version.
This type avoids loss of precision, and is likely what was intended by having 14.4 fixed point. Conversions to/from float64 are provided.
This commit is contained in:
		@@ -256,36 +256,155 @@ func (v *R8) Unmarshal(s string) error {
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FloatFixed14_4 maps a float64 to the SOAP "fixed.14.4" type.
 | 
			
		||||
type FloatFixed14_4 float64
 | 
			
		||||
const Fixed14_4Denominator = 1e4
 | 
			
		||||
const Fixed14_4MaxInteger = 1e14 - 1
 | 
			
		||||
const Fixed14_4MaxFractional = 1e18 - 1
 | 
			
		||||
 | 
			
		||||
var _ SOAPValue = new(FloatFixed14_4)
 | 
			
		||||
 | 
			
		||||
func NewFloatFixed14_4(v float64) *FloatFixed14_4 {
 | 
			
		||||
	v2 := FloatFixed14_4(v)
 | 
			
		||||
	return &v2
 | 
			
		||||
// Fixed14_4 represents a fixed point number with up to 14 decimal digits
 | 
			
		||||
// before the decimal point (integer part), and up to 4 decimal digits
 | 
			
		||||
// after the decimal point (fractional part).
 | 
			
		||||
//
 | 
			
		||||
// Corresponds to the SOAP "fixed.14.4" type.
 | 
			
		||||
//
 | 
			
		||||
// This is a struct to avoid accidentally using the value directly as an
 | 
			
		||||
// integer.
 | 
			
		||||
type Fixed14_4 struct {
 | 
			
		||||
	// Fractional divided by 1e4 is the fixed point value. Take care setting
 | 
			
		||||
	// this directly, it should only contain values in the range (-1e18, 1e18).
 | 
			
		||||
	Fractional int64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *FloatFixed14_4) String() string {
 | 
			
		||||
	return strconv.FormatFloat(float64(*v), 'f', 4, 64)
 | 
			
		||||
var _ SOAPValue = &Fixed14_4{}
 | 
			
		||||
 | 
			
		||||
// Fixed14_4FromParts creates a Fixed14_4 from components.
 | 
			
		||||
// Bounds:
 | 
			
		||||
//   * Both intPart and fracPart must have the same sign.
 | 
			
		||||
//   * -1e14 < intPart < 1e14
 | 
			
		||||
//   * -1e4 < fracPart < 1e4
 | 
			
		||||
func Fixed14_4FromParts(intPart int64, fracPart int16) (Fixed14_4, error) {
 | 
			
		||||
	var v Fixed14_4
 | 
			
		||||
	err := v.SetParts(intPart, fracPart)
 | 
			
		||||
	return v, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *FloatFixed14_4) Marshal() (string, error) {
 | 
			
		||||
	if *v >= 1e14 || *v <= -1e14 {
 | 
			
		||||
		return "", fmt.Errorf("soap fixed14.4: value %v out of bounds", v)
 | 
			
		||||
// SetFromParts sets the value based on the integer component and the fractional component.
 | 
			
		||||
// Bounds:
 | 
			
		||||
//   * Both intPart and fracPart must have the same sign.
 | 
			
		||||
//   * -1e14 < intPart < 1e14
 | 
			
		||||
//   * -1e4 < fracPart < 1e4
 | 
			
		||||
func (v *Fixed14_4) SetParts(intPart int64, fracPart int16) error {
 | 
			
		||||
	if (intPart < 0) != (fracPart < 0) {
 | 
			
		||||
		return fmt.Errorf("want intPart and fracPart with same sign, got %d and %d",
 | 
			
		||||
			intPart, fracPart)
 | 
			
		||||
	}
 | 
			
		||||
	if intPart < -Fixed14_4MaxInteger || intPart > Fixed14_4MaxInteger {
 | 
			
		||||
		return fmt.Errorf("want intPart in range (-1e14,1e14), got %d", intPart)
 | 
			
		||||
	}
 | 
			
		||||
	if fracPart < -Fixed14_4Denominator || fracPart > Fixed14_4Denominator {
 | 
			
		||||
		return fmt.Errorf("want fracPart in range (-1e4,1e4), got %d", fracPart)
 | 
			
		||||
	}
 | 
			
		||||
	v.Fractional = intPart*Fixed14_4Denominator + int64(fracPart)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Returns the integer part and fractional part of the fixed point number.
 | 
			
		||||
func (v Fixed14_4) Parts() (int64, int16) {
 | 
			
		||||
	return v.Fractional / Fixed14_4Denominator, int16(v.Fractional % Fixed14_4Denominator)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Fixed14_4FromFractional creates a Fixed14_4 from an integer, where the
 | 
			
		||||
// parameter divided by 1e4 is the fixed point value.
 | 
			
		||||
func Fixed14_4FromFractional(fracValue int64) (Fixed14_4, error) {
 | 
			
		||||
	var v Fixed14_4
 | 
			
		||||
	err := v.SetFractional(fracValue)
 | 
			
		||||
	return v, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFromFractional sets the value of the fixed point number, where fracValue
 | 
			
		||||
// divided by 1e4 is the fixed point value. Unlike setting v.Fractional
 | 
			
		||||
// directly, this checks the value.
 | 
			
		||||
func (v *Fixed14_4) SetFractional(fracValue int64) error {
 | 
			
		||||
	if fracValue < -Fixed14_4MaxFractional || fracValue > Fixed14_4MaxFractional {
 | 
			
		||||
		return fmt.Errorf("want intPart in range (-1e18,1e18), got %d", fracValue)
 | 
			
		||||
	}
 | 
			
		||||
	v.Fractional = fracValue
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Fixed14_4FromFloat creates a Fixed14_4 from a float64. Returns error if the
 | 
			
		||||
// float is outside the range.
 | 
			
		||||
func Fixed14_4FromFloat(f float64) (Fixed14_4, error) {
 | 
			
		||||
	i := int64(f * Fixed14_4Denominator)
 | 
			
		||||
	return Fixed14_4FromFractional(i)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFloat64 sets the value of the fixed point number from a float64. Returns
 | 
			
		||||
// error if the float is outside the range.
 | 
			
		||||
func (v *Fixed14_4) SetFloat64(f float64) error {
 | 
			
		||||
	i := int64(f * Fixed14_4Denominator)
 | 
			
		||||
	return v.SetFractional(i)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v Fixed14_4) Float64() float64 {
 | 
			
		||||
	return float64(v.Fractional) / Fixed14_4Denominator
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v Fixed14_4) String() string {
 | 
			
		||||
	intPart, fracPart := v.Parts()
 | 
			
		||||
	if fracPart < 0 {
 | 
			
		||||
		fracPart = -fracPart
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%d.%04d", intPart, fracPart)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *Fixed14_4) Marshal() (string, error) {
 | 
			
		||||
	return v.String(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *FloatFixed14_4) Unmarshal(s string) error {
 | 
			
		||||
	v2, err := strconv.ParseFloat(s, 64)
 | 
			
		||||
func (v *Fixed14_4) Unmarshal(s string) error {
 | 
			
		||||
	parts := strings.SplitN(s, ".", 2)
 | 
			
		||||
	intPart, err := strconv.ParseInt(parts[0], 10, 64)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if v2 >= 1e14 || v2 <= -1e14 {
 | 
			
		||||
		return fmt.Errorf("soap fixed14.4: value %q out of bounds", s)
 | 
			
		||||
 | 
			
		||||
	var fracPart int64
 | 
			
		||||
	if len(parts) >= 2 && len(parts[1]) > 0 {
 | 
			
		||||
		fracStr := parts[1]
 | 
			
		||||
 | 
			
		||||
		for _, r := range fracStr {
 | 
			
		||||
			if r < '0' || r > '9' {
 | 
			
		||||
				return fmt.Errorf("found non-digit in fractional component of %q", s)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Take only the 4 most significant digits of the fractional component.
 | 
			
		||||
		fracStr = fracStr[:min(len(fracStr), 4)]
 | 
			
		||||
 | 
			
		||||
		fracPart, err = strconv.ParseInt(fracStr, 10, 16)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if fracPart < 0 {
 | 
			
		||||
			// This shouldn't happen by virtue of earlier digit-only check.
 | 
			
		||||
			return fmt.Errorf("got negative fractional component in %q", s)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch len(fracStr) {
 | 
			
		||||
		case 1:
 | 
			
		||||
			fracPart *= 1000
 | 
			
		||||
		case 2:
 | 
			
		||||
			fracPart *= 100
 | 
			
		||||
		case 3:
 | 
			
		||||
			fracPart *= 10
 | 
			
		||||
		case 4:
 | 
			
		||||
			fracPart *= 1
 | 
			
		||||
		}
 | 
			
		||||
		if intPart < 0 {
 | 
			
		||||
			fracPart = -fracPart
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	*v = FloatFixed14_4(v2)
 | 
			
		||||
	v.SetParts(intPart, int16(fracPart))
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -609,6 +728,62 @@ func (v *TimeOfDayTZ) Unmarshal(s string) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Date maps to the SOAP "date" type. Marshaling and Unmarshalling does *not*
 | 
			
		||||
// check if components are in range.
 | 
			
		||||
type Date struct {
 | 
			
		||||
	Year  int
 | 
			
		||||
	Month time.Month
 | 
			
		||||
	Day   int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var _ SOAPValue = &Date{}
 | 
			
		||||
 | 
			
		||||
func DateFromTime(t time.Time) Date {
 | 
			
		||||
	var d Date
 | 
			
		||||
	d.SetFromTime(t)
 | 
			
		||||
	return d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Date) SetFromTime(t time.Time) {
 | 
			
		||||
	d.Year = t.Year()
 | 
			
		||||
	d.Month = t.Month()
 | 
			
		||||
	d.Day = t.Day()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToTime returns a time.Time from the date components, at midnight, and using
 | 
			
		||||
// the given location.
 | 
			
		||||
func (d *Date) ToTime(loc *time.Location) time.Time {
 | 
			
		||||
	return time.Date(d.Year, d.Month, d.Day, 0, 0, 0, 0, loc)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Date) String() string {
 | 
			
		||||
	return fmt.Sprintf("%04d-%02d-%02d", d.Year, d.Month, d.Day)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckValid returns an error if the date components are out of range.
 | 
			
		||||
func (d *Date) CheckValid() error {
 | 
			
		||||
	y, m, day := d.ToTime(time.UTC).Date()
 | 
			
		||||
	if y != d.Year || m != d.Month || day != d.Day {
 | 
			
		||||
		return fmt.Errorf("SOAP date component(s) out of range in %v", d)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Date) Marshal() (string, error) {
 | 
			
		||||
	return d.String(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Date) Unmarshal(s string) error {
 | 
			
		||||
	year, month, day, err := parseDateParts(s)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	d.Year = year
 | 
			
		||||
	d.Month = time.Month(month)
 | 
			
		||||
	d.Day = day
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DateLocal maps time.Time to the SOAP "date" type. Dates map to midnight in
 | 
			
		||||
// the local time zone. The time of day components are ignored when
 | 
			
		||||
// marshalling.
 | 
			
		||||
@@ -862,3 +1037,10 @@ func (v *URI) Unmarshal(s string) error {
 | 
			
		||||
	*v = URI(*v2)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func min(a, b int) int {
 | 
			
		||||
	if a < b {
 | 
			
		||||
		return a
 | 
			
		||||
	}
 | 
			
		||||
	return b
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,10 +3,19 @@ package types
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"math"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func newFixed14_4Parts(intPart int64, fracPart int16) *Fixed14_4 {
 | 
			
		||||
	v, err := Fixed14_4FromParts(intPart, fracPart)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	return &v
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type isEqual func(got, want SOAPValue) bool
 | 
			
		||||
 | 
			
		||||
type typeTestCase struct {
 | 
			
		||||
@@ -135,20 +144,37 @@ func Test(t *testing.T) {
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			makeValue: func() SOAPValue { return new(FloatFixed14_4) },
 | 
			
		||||
			isEqual:   func(got, want SOAPValue) bool { return *got.(*FloatFixed14_4) == *want.(*FloatFixed14_4) },
 | 
			
		||||
			marshalTests: []marshalCase{
 | 
			
		||||
				{NewFloatFixed14_4(0), "0.0000"},
 | 
			
		||||
				{NewFloatFixed14_4(1), "1.0000"},
 | 
			
		||||
				{NewFloatFixed14_4(1.2346), "1.2346"},
 | 
			
		||||
				{NewFloatFixed14_4(-1), "-1.0000"},
 | 
			
		||||
				{NewFloatFixed14_4(-1.2346), "-1.2346"},
 | 
			
		||||
				{NewFloatFixed14_4(1e13), "10000000000000.0000"},
 | 
			
		||||
				{NewFloatFixed14_4(-1e13), "-10000000000000.0000"},
 | 
			
		||||
			makeValue: func() SOAPValue { return &Fixed14_4{} },
 | 
			
		||||
			isEqual: func(got, want SOAPValue) bool {
 | 
			
		||||
				return got.(*Fixed14_4).Fractional == want.(*Fixed14_4).Fractional
 | 
			
		||||
			},
 | 
			
		||||
			marshalErrs: []SOAPValue{
 | 
			
		||||
				NewFloatFixed14_4(1e14),
 | 
			
		||||
				NewFloatFixed14_4(-1e14),
 | 
			
		||||
			marshalTests: []marshalCase{
 | 
			
		||||
				{newFixed14_4Parts(0, 0), "0.0000"},
 | 
			
		||||
				{newFixed14_4Parts(1, 2), "1.0002"},
 | 
			
		||||
				{newFixed14_4Parts(1, 20), "1.0020"},
 | 
			
		||||
				{newFixed14_4Parts(1, 200), "1.0200"},
 | 
			
		||||
				{newFixed14_4Parts(1, 2000), "1.2000"},
 | 
			
		||||
				{newFixed14_4Parts(-1, -2), "-1.0002"},
 | 
			
		||||
				{newFixed14_4Parts(1234, 5678), "1234.5678"},
 | 
			
		||||
				{newFixed14_4Parts(-1234, -5678), "-1234.5678"},
 | 
			
		||||
				{newFixed14_4Parts(9999_99999_99999, 9999), "99999999999999.9999"},
 | 
			
		||||
				{newFixed14_4Parts(-9999_99999_99999, -9999), "-99999999999999.9999"},
 | 
			
		||||
			},
 | 
			
		||||
			unmarshalErrs: append([]string{
 | 
			
		||||
				"", ".", "0.00000000abc", "0.-5",
 | 
			
		||||
			}, badNumbers...),
 | 
			
		||||
			unmarshalTests: []unmarshalCase{
 | 
			
		||||
				{"010", newFixed14_4Parts(10, 0)},
 | 
			
		||||
				{"0", newFixed14_4Parts(0, 0)},
 | 
			
		||||
				{"0.", newFixed14_4Parts(0, 0)},
 | 
			
		||||
				{"0.000005", newFixed14_4Parts(0, 0)},
 | 
			
		||||
				{"1.2", newFixed14_4Parts(1, 2000)},
 | 
			
		||||
				{"1.20", newFixed14_4Parts(1, 2000)},
 | 
			
		||||
				{"1.200", newFixed14_4Parts(1, 2000)},
 | 
			
		||||
				{"1.02", newFixed14_4Parts(1, 200)},
 | 
			
		||||
				{"1.020", newFixed14_4Parts(1, 200)},
 | 
			
		||||
				{"1.002", newFixed14_4Parts(1, 20)},
 | 
			
		||||
				{"1.00200005", newFixed14_4Parts(1, 20)},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
@@ -422,3 +448,91 @@ func Test(t *testing.T) {
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFixed14_4(t *testing.T) {
 | 
			
		||||
	t.Run("Parts", func(t *testing.T) {
 | 
			
		||||
		tests := []struct {
 | 
			
		||||
			intPart    int64
 | 
			
		||||
			fracPart   int16
 | 
			
		||||
			fractional int64
 | 
			
		||||
		}{
 | 
			
		||||
			{0, 0, 0},
 | 
			
		||||
			{1, 2, 1_0002},
 | 
			
		||||
			{-1, -2, -1_0002},
 | 
			
		||||
			{1234, 5678, 1234_5678},
 | 
			
		||||
			{-1234, -5678, -1234_5678},
 | 
			
		||||
			{9999_99999_99999, 9999, 9999_99999_99999_9999},
 | 
			
		||||
			{-9999_99999_99999, -9999, -9999_99999_99999_9999},
 | 
			
		||||
		}
 | 
			
		||||
		for _, test := range tests {
 | 
			
		||||
			test := test
 | 
			
		||||
			t.Run(fmt.Sprintf("FromParts(%d,%d)", test.intPart, test.fracPart), func(t *testing.T) {
 | 
			
		||||
				got, err := Fixed14_4FromParts(test.intPart, test.fracPart)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					t.Errorf("got error %v, want success", err)
 | 
			
		||||
				}
 | 
			
		||||
				if got.Fractional != test.fractional {
 | 
			
		||||
					t.Errorf("got %d, want %d", got.Fractional, test.fractional)
 | 
			
		||||
				}
 | 
			
		||||
			})
 | 
			
		||||
			t.Run(fmt.Sprintf("%d.Parts()", test.fractional), func(t *testing.T) {
 | 
			
		||||
				v, err := Fixed14_4FromFractional(test.fractional)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					t.Errorf("got error %v, want success", err)
 | 
			
		||||
				}
 | 
			
		||||
				gotIntPart, gotFracPart := v.Parts()
 | 
			
		||||
				if gotIntPart != test.intPart {
 | 
			
		||||
					t.Errorf("got %d, want %d", gotIntPart, test.intPart)
 | 
			
		||||
				}
 | 
			
		||||
				if gotFracPart != test.fracPart {
 | 
			
		||||
					t.Errorf("got %d, want %d", gotFracPart, test.fracPart)
 | 
			
		||||
				}
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
	t.Run("Float", func(t *testing.T) {
 | 
			
		||||
		tests := []struct {
 | 
			
		||||
			flt float64
 | 
			
		||||
			fix *Fixed14_4
 | 
			
		||||
		}{
 | 
			
		||||
			{0, newFixed14_4Parts(0, 0)},
 | 
			
		||||
			{1234.5678, newFixed14_4Parts(1234, 5678)},
 | 
			
		||||
			{-1234.5678, newFixed14_4Parts(-1234, -5678)},
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, test := range tests {
 | 
			
		||||
			t.Run(fmt.Sprintf("To/FromFloat(%v)", test.fix), func(t *testing.T) {
 | 
			
		||||
				gotFix, err := Fixed14_4FromFloat(test.flt)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					t.Errorf("got error %v, want success", err)
 | 
			
		||||
				}
 | 
			
		||||
				if gotFix.Fractional != test.fix.Fractional {
 | 
			
		||||
					t.Errorf("got %v, want %v", gotFix, test.fix)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				gotFlt := test.fix.Float64()
 | 
			
		||||
				if math.Abs(gotFlt-test.flt) > 1e-6 {
 | 
			
		||||
					t.Errorf("got %f, want %f", gotFlt, test.flt)
 | 
			
		||||
				}
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		errTests := []float64{
 | 
			
		||||
			1e50,
 | 
			
		||||
			-1e50,
 | 
			
		||||
			1e14,
 | 
			
		||||
			-1e14,
 | 
			
		||||
			math.NaN(),
 | 
			
		||||
			math.Inf(1),
 | 
			
		||||
			math.Inf(-1),
 | 
			
		||||
		}
 | 
			
		||||
		for _, test := range errTests {
 | 
			
		||||
			t.Run(fmt.Sprintf("ErrorFromFloat(%f)", test), func(t *testing.T) {
 | 
			
		||||
				got, err := Fixed14_4FromFloat(test)
 | 
			
		||||
				if err == nil {
 | 
			
		||||
					t.Errorf("got success and %v, want error", got)
 | 
			
		||||
				}
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user