Addressing rounding and overflow issues in NewFromFloatWithExponent (#77)

* Additional (and some breaking) tests for NewFromFloatWithExponent

* Addressing tests for NewFromFloatWithExponent

* Naming cosmetic correction

* removing unused code

* Improving FromFloatWithExponent

* Addressing special meaning of zero exponent in float64

* NewFromFloatWithExponent: subnormals support

* NewFromFloatWithExponent: just a few additional test cases

* NewFromFloatWithExponent: optimization and some documentation

* NewFromFloatWithExponent: optimizations

* NewFromFloatWithExponent: optimizations
This commit is contained in:
Igor Mikushkin 2018-03-02 01:29:13 +04:00 committed by Victor Quinn
parent e3482495ff
commit bf9a39e28b
2 changed files with 106 additions and 21 deletions

View file

@ -188,15 +188,82 @@ func NewFromFloat(value float64) Decimal {
// NewFromFloatWithExponent(123.456, -2).String() // output: "123.46"
//
func NewFromFloatWithExponent(value float64, exp int32) Decimal {
mul := math.Pow(10, -float64(exp))
floatValue := value * mul
if math.IsNaN(floatValue) || math.IsInf(floatValue, 0) {
panic(fmt.Sprintf("Cannot create a Decimal from %v", floatValue))
if math.IsNaN(value) || math.IsInf(value, 0) {
panic(fmt.Sprintf("Cannot create a Decimal from %v", value))
}
bits := math.Float64bits(value)
mant := bits & (1<<52 - 1)
exp2 := int32((bits >> 52) & (1<<11 - 1))
sign := bits >> 63
if exp2 == 0 {
// specials
if mant == 0 {
return Decimal{}
} else {
// subnormal
exp2++
}
} else {
// normal
mant |= 1 << 52
}
exp2 -= 1023 + 52
// normalizing base-2 values
for mant&1 == 0 {
mant = mant >> 1
exp2++
}
// maximum number of fractional base-10 digits to represent 2^N exactly cannot be more than -N if N<0
if exp < 0 && exp < exp2 {
if exp2 < 0 {
exp = exp2
} else {
exp = 0
}
}
// representing 10^M * 2^N as 5^M * 2^(M+N)
exp2 -= exp
temp := big.NewInt(1)
dMant := big.NewInt(int64(mant))
// applying 5^M
if exp > 0 {
temp = temp.SetInt64(int64(exp))
temp = temp.Exp(fiveInt, temp, nil)
} else if exp < 0 {
temp = temp.SetInt64(-int64(exp))
temp = temp.Exp(fiveInt, temp, nil)
dMant = dMant.Mul(dMant, temp)
temp = temp.SetUint64(1)
}
// applying 2^(M+N)
if exp2 > 0 {
dMant = dMant.Lsh(dMant, uint(exp2))
} else if exp2 < 0 {
temp = temp.Lsh(temp, uint(-exp2))
}
// rounding and downscaling
if exp > 0 || exp2 < 0 {
halfDown := new(big.Int).Rsh(temp, 1)
dMant = dMant.Add(dMant, halfDown)
dMant = dMant.Quo(dMant, temp)
}
if sign == 1 {
dMant = dMant.Neg(dMant)
}
dValue := big.NewInt(round(floatValue))
return Decimal{
value: dValue,
value: dMant,
exp: exp,
}
}
@ -972,13 +1039,6 @@ func min(x, y int32) int32 {
return x
}
func round(n float64) int64 {
if n < 0 {
return int64(n - 0.5)
}
return int64(n + 0.5)
}
func unquoteIfQuoted(value interface{}) (string, error) {
var bytes []byte

View file

@ -191,20 +191,45 @@ func TestNewFromFloatWithExponent(t *testing.T) {
float float64
exp int32
}
// some tests are taken from here https://www.cockroachlabs.com/blog/rounding-implementations-in-go/
tests := map[Inp]string{
Inp{123.4, -3}: "123.4",
Inp{123.4, -1}: "123.4",
Inp{123.412345, 1}: "120",
Inp{123.412345, 0}: "123",
Inp{123.412345, -5}: "123.41235",
Inp{123.412345, -6}: "123.412345",
Inp{123.412345, -7}: "123.412345",
Inp{123.4, -3}: "123.4",
Inp{123.4, -1}: "123.4",
Inp{123.412345, 1}: "120",
Inp{123.412345, 0}: "123",
Inp{123.412345, -5}: "123.41235",
Inp{123.412345, -6}: "123.412345",
Inp{123.412345, -7}: "123.412345",
Inp{123.412345, -28}: "123.4123450000000019599610823207",
Inp{1230000000, 3}: "1230000000",
Inp{123.9999999999999999, -7}: "124",
Inp{123.8989898999999999, -7}: "123.8989899",
Inp{0.49999999999999994, 0}: "0",
Inp{0.5, 0}: "1",
Inp{0., -1000}: "0",
Inp{0.5000000000000001, 0}: "1",
Inp{1.390671161567e-309, 0}: "0",
Inp{4.503599627370497e+15, 0}: "4503599627370497",
Inp{4.503599627370497e+60, 0}: "4503599627370497110902645731364739935039854989106233267453952",
Inp{4.503599627370497e+60, 1}: "4503599627370497110902645731364739935039854989106233267453950",
Inp{4.503599627370497e+60, -1}: "4503599627370497110902645731364739935039854989106233267453952",
Inp{50, 2}: "100",
Inp{49, 2}: "0",
Inp{50, 3}: "0",
// subnormals
Inp{1.390671161567e-309, -2000}: "0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001390671161567000864431395448332752540137009987788957394095829635554502771758698872408926974382819387852542087331897381878220271350970912568035007740861074263206736245957501456549756342151614772544950978154339064833880234531754156635411349342950306987480369774780312897442981323940546749863054846093718407237782253156822124910364044261653195961209878120072488178603782495270845071470243842997312255994555557251870400944414666445871039673491570643357351279578519863428540219295076767898526278029257129758694673164251056158277568765100904638511604478844087596428177947970563689475826736810456067108202083804368114484417399279328807983736233036662284338182105684628835292230438999173947056675615385756827890872955322265625",
Inp{1.390671161567e-309, -862}: "0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013906711615670008644313954483327525401370099877889573940958296355545027717586988724089269743828193878525420873318973818782202713509709125680350077408610742632067362459575014565497563421516147725449509781543390648338802345317541566354113493429503069874803697747803128974429813239405467498630548460937184072377822531568221249103640442616531959612098781200724881786037824952708450714702438429973122559945555572518704009444146664458710396734915706433573512795785198634285402192950767678985262780292571297586946731642510561582775687651009046385116044788440876",
Inp{1.390671161567e-309, -863}: "0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013906711615670008644313954483327525401370099877889573940958296355545027717586988724089269743828193878525420873318973818782202713509709125680350077408610742632067362459575014565497563421516147725449509781543390648338802345317541566354113493429503069874803697747803128974429813239405467498630548460937184072377822531568221249103640442616531959612098781200724881786037824952708450714702438429973122559945555572518704009444146664458710396734915706433573512795785198634285402192950767678985262780292571297586946731642510561582775687651009046385116044788440876",
}
// add negatives
for p, s := range tests {
if p.float > 0 {
tests[Inp{-p.float, p.exp}] = "-" + s
if s != "0" {
tests[Inp{-p.float, p.exp}] = "-" + s
} else {
tests[Inp{-p.float, p.exp}] = "0"
}
}
}