Skip to content

Commit

Permalink
cty: Maintain precision correctly in Multiply and Modulo
Browse files Browse the repository at this point in the history
When working with big.Float values, it is important to ensure the
desired precision for the operations is maintained throughout. The
default precision for a Float is set initially by the max argument
precision when creating the non-zero value. If this is coming from a
Float which was set via SetInt, and that Int value was created by
SetInt64, then the precision is only 64 bits.
  • Loading branch information
jbardin authored Nov 12, 2020
1 parent 1338293 commit 4b8bae2
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 5 deletions.
30 changes: 25 additions & 5 deletions cty/value_ops.go
Original file line number Diff line number Diff line change
Expand Up @@ -596,8 +596,25 @@ func (val Value) Multiply(other Value) Value {
return *shortCircuit
}

ret := new(big.Float)
// find the larger precision of the arguments
resPrec := val.v.(*big.Float).Prec()
otherPrec := other.v.(*big.Float).Prec()
if otherPrec > resPrec {
resPrec = otherPrec
}

// make sure we have enough precision for the product
ret := new(big.Float).SetPrec(512)
ret.Mul(val.v.(*big.Float), other.v.(*big.Float))

// now reduce the precision back to the greater argument, or the minimum
// required by the product.
minPrec := ret.MinPrec()
if minPrec > resPrec {
resPrec = minPrec
}
ret.SetPrec(resPrec)

return NumberVal(ret)
}

Expand Down Expand Up @@ -669,11 +686,14 @@ func (val Value) Modulo(other Value) Value {
// FIXME: This is a bit clumsy. Should come back later and see if there's a
// more straightforward way to do this.
rat := val.Divide(other)
ratFloorInt := &big.Int{}
rat.v.(*big.Float).Int(ratFloorInt)
work := (&big.Float{}).SetInt(ratFloorInt)
ratFloorInt, _ := rat.v.(*big.Float).Int(nil)

// start with a copy of the original larger value so that we do not lose
// precision.
v := val.v.(*big.Float)
work := new(big.Float).Copy(v).SetInt(ratFloorInt)
work.Mul(other.v.(*big.Float), work)
work.Sub(val.v.(*big.Float), work)
work.Sub(v, work)

return NumberVal(work)
}
Expand Down
16 changes: 16 additions & 0 deletions cty/value_ops_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1777,6 +1777,17 @@ func TestValueMultiply(t *testing.T) {
Zero.Mark(2),
Zero.WithMarks(NewValueMarks(1, 2)),
},
{
MustParseNumberVal("967323432120515089486873574508975134568969931547"),
NumberFloatVal(12345),
MustParseNumberVal("11941607769527758779715454277313298036253933804947715"),
},
//
{
NumberFloatVal(22337203685475.5),
NumberFloatVal(22337203685475.5),
MustParseNumberVal("498950668486420259929661100.25"),
},
}

for _, test := range tests {
Expand Down Expand Up @@ -1953,6 +1964,11 @@ func TestValueModulo(t *testing.T) {
NumberIntVal(10).Mark(2),
Zero.WithMarks(NewValueMarks(1, 2)),
},
{
MustParseNumberVal("967323432120515089486873574508975134568969931547"),
NumberIntVal(10),
NumberIntVal(7),
},
}

for _, test := range tests {
Expand Down

0 comments on commit 4b8bae2

Please sign in to comment.