mirror of
https://github.com/shopspring/decimal.git
synced 2024-11-22 20:40:48 +01:00
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:
parent
e3482495ff
commit
bf9a39e28b
2 changed files with 106 additions and 21 deletions
86
decimal.go
86
decimal.go
|
@ -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
|
||||
|
||||
|
|
|
@ -191,6 +191,7 @@ 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",
|
||||
|
@ -199,12 +200,36 @@ func TestNewFromFloatWithExponent(t *testing.T) {
|
|||
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 {
|
||||
if s != "0" {
|
||||
tests[Inp{-p.float, p.exp}] = "-" + s
|
||||
} else {
|
||||
tests[Inp{-p.float, p.exp}] = "0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue