diff --git a/src/math/big/decimal.go b/src/math/big/decimal.go new file mode 100644 index 0000000000..f4c535acdb --- /dev/null +++ b/src/math/big/decimal.go @@ -0,0 +1,191 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements multi-precision decimal numbers. +// The implementation is for float to decimal conversion only; +// not general purpose use. +// The only operations are precise conversion from binary to +// decimal and rounding. +// +// The key observation and some code (shr) is borrowed from +// strconv/decimal.go: conversion of binary fractional values can be done +// precisely in multi-precision decimal because 2 divides 10 (required for +// >> of mantissa); but conversion of decimal floating-point values cannot +// be done precisely in binary representation. +// +// In contrast to strconv/decimal.go, only right shift is implemented in +// decimal format - left shift can be done precisely in binary format. + +package big + +// A decimal represents a floating-point number in decimal representation. +// The value of a decimal x is x.mant * 10 ** x.exp with 0.5 <= x.mant < 1, +// with the most-significant mantissa digit at index 0. +type decimal struct { + mant []byte // mantissa ASCII digits, big-endian + exp int // exponent, valid if len(mant) > 0 +} + +// Maximum shift amount that can be done in one pass without overflow. +// A Word has _W bits and (1<= 0), or m >> -shift (for shift < 0). +func (x *decimal) init(m nat, shift int) { + // special case 0 + if len(m) == 0 { + x.mant = x.mant[:0] + return + } + + // Optimization: If we need to shift right, first remove any trailing + // zero bits from m to reduce shift amount that needs to be done in + // decimal format (since that is likely slower). + if shift < 0 { + ntz := m.trailingZeroBits() + s := uint(-shift) + if s >= ntz { + s = ntz // shift at most ntz bits + } + m = nat(nil).shr(m, s) + shift += int(s) + } + + // Do any shift left in binary representation. + if shift > 0 { + m = nat(nil).shl(m, uint(shift)) + shift = 0 + } + + // Convert mantissa into decimal representation. + s := m.decimalString() // TODO(gri) avoid string conversion here + n := len(s) + x.exp = n + // Trim trailing zeros; instead the exponent is tracking + // the decimal point independent of the number of digits. + for n > 0 && s[n-1] == 0 { + n-- + } + x.mant = make([]byte, n) + copy(x.mant, s) + + // Do any (remaining) shift right in decimal representation. + if shift < 0 { + for shift < -maxShift { + x.shr(maxShift) + shift += maxShift + } + x.shr(uint(-shift)) + } +} + +// Possibly optimization: The current implementation of nat.string takes +// a charset argument. When a right shift is needed, we could provide +// "\x00\x01...\x09" instead of "012..9" (as in nat.decimalString) and +// avoid the repeated +'0' and -'0' operations in decimal.shr (and do a +// single +'0' pass at the end). + +// shr implements x >> s, for s <= maxShift. +func (x *decimal) shr(s uint) { + // Division by 1<>s == 0 && r < len(x.mant) { + ch := Word(x.mant[r]) + r++ + n = n*10 + ch - '0' + } + if n == 0 { + // x == 0; shouldn't get here, but handle anyway + x.mant = x.mant[:0] + return + } + for n>>s == 0 { + r++ + n *= 10 + } + x.exp += 1 - r + + // read a digit, write a digit + w := 0 // write index + for r < len(x.mant) { + ch := Word(x.mant[r]) + r++ + d := n >> s + n -= d << s + x.mant[w] = byte(d + '0') + w++ + n = n*10 + ch - '0' + } + + // write extra digits that still fit + for n > 0 && w < len(x.mant) { + d := n >> s + n -= d << s + x.mant[w] = byte(d + '0') + w++ + n = n * 10 + } + x.mant = x.mant[:w] // the number may be shorter (e.g. 1024 >> 10) + + // append additional digits that didn't fit + for n > 0 { + d := n >> s + n -= d << s + x.mant = append(x.mant, byte(d+'0')) + n = n * 10 + } + + // remove trailing zeros + w = len(x.mant) + for w > 0 && x.mant[w-1] == '0' { + w-- + } + x.mant = x.mant[:w] +} + +func (x *decimal) String() string { + if len(x.mant) == 0 { + return "0" + } + + var buf []byte + switch { + case x.exp <= 0: + // 0.00ddd + buf = append(buf, "0."...) + buf = appendZeros(buf, -x.exp) + buf = append(buf, x.mant...) + + case /* 0 < */ x.exp < len(x.mant): + // dd.ddd + buf = append(buf, x.mant[:x.exp]...) + buf = append(buf, '.') + buf = append(buf, x.mant[x.exp:]...) + + default: // len(x.mant) <= x.exp + // ddd00 + buf = append(buf, x.mant...) + buf = appendZeros(buf, x.exp-len(x.mant)) + } + + return string(buf) +} + +// appendZeros appends n 0 digits to buf and returns buf. +func appendZeros(buf []byte, n int) []byte { + for ; n > 0; n-- { + buf = append(buf, '0') + } + return buf +} diff --git a/src/math/big/decimal_test.go b/src/math/big/decimal_test.go new file mode 100644 index 0000000000..ce20800ef0 --- /dev/null +++ b/src/math/big/decimal_test.go @@ -0,0 +1,51 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package big + +import "testing" + +func TestDecimalString(t *testing.T) { + for _, test := range []struct { + x decimal + want string + }{ + {want: "0"}, + {decimal{nil, 1000}, "0"}, // exponent of 0 is ignored + {decimal{[]byte("12345"), 0}, "0.12345"}, + {decimal{[]byte("12345"), -3}, "0.00012345"}, + {decimal{[]byte("12345"), +3}, "123.45"}, + {decimal{[]byte("12345"), +10}, "1234500000"}, + } { + if got := test.x.String(); got != test.want { + t.Errorf("%v == %s; want %s", test.x, got, test.want) + } + } +} + +func TestDecimalInit(t *testing.T) { + for _, test := range []struct { + x Word + shift int + want string + }{ + {0, 0, "0"}, + {0, -100, "0"}, + {0, 100, "0"}, + {1, 0, "1"}, + {1, 10, "1024"}, + {1, 100, "1267650600228229401496703205376"}, + {1, -100, "0.0000000000000000000000000000007888609052210118054117285652827862296732064351090230047702789306640625"}, + {12345678, 8, "3160493568"}, + {12345678, -8, "48225.3046875"}, + {195312, 9, "99999744"}, + {1953125, 9, "1000000000"}, + } { + var d decimal + d.init(nat{test.x}.norm(), test.shift) + if got := d.String(); got != test.want { + t.Errorf("%d << %d == %s; want %s", test.x, test.shift, got, test.want) + } + } +}