Skip to content

Commit 0f806d4

Browse files
committed
Simple decimal floating point
1 parent d557455 commit 0f806d4

File tree

6 files changed

+612
-0
lines changed

6 files changed

+612
-0
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
*.jl.cov
2+
*.jl.*.cov
3+
*.jl.mem
4+
.DS_Store

.travis.yml

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
## Documentation: http://docs.travis-ci.com/user/languages/julia/
2+
language: julia
3+
os:
4+
- linux
5+
- osx
6+
julia:
7+
- 1.0
8+
- 1.1
9+
- nightly
10+
notifications:
11+
email: false
12+
git:
13+
depth: 99999999
14+
15+
## uncomment the following lines to allow failures on nightly julia
16+
## (tests will run but not make your overall status red)
17+
matrix:
18+
allow_failures:
19+
- julia: nightly
20+
21+
## uncomment and modify the following lines to manually install system packages
22+
#addons:
23+
# apt: # apt-get for linux
24+
# packages:
25+
# - gfortran
26+
#before_script: # homebrew for mac
27+
# - if [ $TRAVIS_OS_NAME = osx ]; then brew install gcc; fi
28+
29+
## uncomment the following lines to override the default test script
30+
#script:
31+
32+
after_success:
33+
# push coverage results to Coveralls & Codecov
34+
- julia -e 'using Pkg; cd(Pkg.dir("Decs")); Pkg.add("Coverage"); using Coverage; Coveralls.submit(Coveralls.process_folder()); Codecov.submit(Codecov.process_folder())'

Project.toml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
name = "Decs"
2+
desc = "Simple arbitrary precision decimal floating point"
3+
uuid = "27b11802-3905-11e9-3b1d-e1305f76eaae"
4+
authors = ["ScottPJones <[email protected]>"]
5+
keywords = ["Decimal", "Floating Point"]
6+
license = "MIT"
7+
8+
[extras]
9+
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
10+
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
11+
12+
[targets]
13+
test = ["Test", "BenchmarkTools"]

appveyor.yml

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
environment:
2+
matrix:
3+
- julia_version: 1.0
4+
- julia_version: 1.1
5+
- julia_version: latest
6+
7+
platform:
8+
- x86 # 32-bit
9+
- x64 # 64-bit
10+
11+
## uncomment the following lines to allow failures on nightly julia
12+
## (tests will run but not make your overall status red)
13+
matrix:
14+
allow_failures:
15+
- julia_version: latest
16+
17+
branches:
18+
only:
19+
- master
20+
- /release-.*/
21+
22+
notifications:
23+
- provider: Email
24+
on_build_success: false
25+
on_build_failure: false
26+
on_build_status_changed: false
27+
28+
install:
29+
- ps: iex ((new-object net.webclient).DownloadString("https://raw.githubusercontent.com/JuliaCI/Appveyor.jl/version-1/bin/install.ps1"))
30+
31+
build_script:
32+
- echo "%JL_BUILD_SCRIPT%"
33+
- C:\julia\bin\julia -e "%JL_BUILD_SCRIPT%"
34+
35+
test_script:
36+
- echo "%JL_TEST_SCRIPT%"
37+
- C:\julia\bin\julia -e "%JL_TEST_SCRIPT%"

src/Decs.jl

+279
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
"""
2+
Decs package
3+
4+
Copyright 2019 Gandalf Software, Inc., Scott P. Jones
5+
Licensed under MIT License, see LICENSE.md
6+
Originally based/inspired by Decimals.jl (by [email protected] (Jack Peterson), 7/3/2014)
7+
"""
8+
module Decs
9+
10+
macro dec_str(s, flags...) parse(Dec, s) end
11+
12+
import Base: ==, +, -, *, /, <, float, inv, round
13+
14+
export Dec, dec, @dec_str, number, normalize
15+
16+
const DIGITS = 20
17+
18+
const DFP_MARKER = BigInt(0)
19+
20+
struct Dec <: AbstractFloat
21+
sgn::Bool # true = negative
22+
pow::Int32 # power of 10
23+
val::BigInt # value
24+
end
25+
26+
Dec(val::Integer, scl) = Dec(val < 0, scl, abs(val))
27+
28+
const dectab = Vector{BigInt}(undef, 38)
29+
30+
function __init__()
31+
for p = 1:38; dectab[p] = BigInt(10)^p; end
32+
end
33+
34+
# Primitives to help make these functions more generic later on
35+
36+
const IntDec = Union{Integer, Dec}
37+
38+
_getsign(x::Dec) = x.sgn
39+
_getsign(x::Unsigned) = false
40+
_getsign(x::Integer) = x < 0
41+
42+
_getcoeff(x::Dec) = x.val
43+
_getcoeff(x::Integer) = BigInt(abs(x))
44+
45+
_getint(x::Dec) = x.val
46+
_getint(x::Integer) = abs(x)
47+
48+
_getscale(x::Dec) = x.pow
49+
_getscale(x::Integer) = 0
50+
51+
_scale(x, d) = _getcoeff(x) * (d > length(dectab) ? BigInt(10)^d : dectab[d])
52+
53+
_eq(x::IntDec, y::IntDec) = _getint(x) == _getint(y)
54+
55+
_lt(x::IntDec, y::IntDec) = _getint(x) < _getint(y)
56+
57+
# Promotion rules
58+
Base.promote_rule(::Type{Dec}, ::Type{<:Real}) = Dec
59+
60+
# override definitions in Base
61+
Base.promote_rule(::Type{BigFloat}, ::Type{Dec}) = Dec
62+
Base.promote_rule(::Type{BigInt}, ::Type{Dec}) = Dec
63+
64+
# Addition
65+
function +(x::Dec, y::IntDec)
66+
# Quickly deal with zero case, so as not to worry about -0.0 cases
67+
iszero(x) && return y
68+
iszero(y) && return x
69+
# Make both the same scale
70+
xscl = _getscale(x)
71+
yscl = _getscale(y)
72+
dscl = xscl - yscl
73+
if dscl == 0
74+
xval = _getcoeff(x)
75+
yval = _getcoeff(y)
76+
else
77+
xval, yval = dscl < 0 ? (_getint(x), _scale(y, -dscl)) : (_scale(x, dscl), _getint(y))
78+
abs(xscl) < abs(yscl) && (xscl = yscl)
79+
end
80+
# Simple case where signs are the same
81+
(xsgn = _getsign(x)) == _getsign(y) && return Dec(xsgn, xscl, xval + yval)
82+
# Signs are different, we need to subtract, possibly change sign
83+
(diff = xval - yval) < 0 ? Dec(!xsgn, xscl, -diff) : Dec(xsgn, xscl, diff)
84+
end
85+
+(x::Integer, y::Dec) = y + x
86+
87+
# Negation
88+
-(x::Dec) = Dec(!x.sgn, x.pow, x.val)
89+
90+
# Subtraction
91+
-(x::Dec, y::IntDec) = x + -y
92+
-(x::Integer, y::Dec) = -x + y
93+
94+
# Multiplication
95+
*(x::Dec, y::IntDec) =
96+
Dec(xor(_getsign(x), _getsign(y)), _getscale(x) + _getscale(y), _getint(x) * _getint(y))
97+
*(x::Integer, y::Dec) = y * x
98+
99+
# Inversion
100+
function Base.inv(x::Dec)
101+
str = string(x)
102+
if str[1] == '-'
103+
str = str[2:end]
104+
end
105+
b = ('.' in str) ? length(split(str, '.')[1]) : 0
106+
c = round(BigInt(10)^(-x.pow + DIGITS) / x.val)
107+
q = (x.pow < 0) ? 1 - b - DIGITS : -b - DIGITS
108+
normalize(Dec(x.sgn, q, c))
109+
end
110+
111+
# Division
112+
/(x::Dec, y::Dec) = x * inv(y)
113+
114+
# TODO exponentiation
115+
116+
# Convert a string to a decimal, e.g. "0.01" -> Dec(0, -2, 1)
117+
function Base.parse(::Type{Dec}, str::AbstractString)
118+
'e' in str && return parse(Dec, scinote(str))
119+
c, q = parameters(('.' in str) ? split(str, '.') : str)
120+
Dec((str[1] == '-') ? 1 : 0, q, c)
121+
end
122+
123+
dec(str::AbstractString) = parse(Dec, str)
124+
125+
# Convert a number to a decimal
126+
Dec(num::Real) = parse(Dec, string(num))
127+
Base.convert(::Type{Dec}, num::Real) = Dec(num::Real)
128+
dec(x::Real) = Dec(x)
129+
Dec(x::Dec) = x
130+
131+
# Get Dec constructor parameters from string
132+
parameters(x::AbstractString) = (abs(parse(BigInt, x)), 0)
133+
134+
# Get Dec constructor parameters from array
135+
function parameters(x::Array)
136+
c = parse(BigInt, join(x))
137+
(abs(c), -length(x[2]))
138+
end
139+
140+
const strzeros = repeat('0', 256)
141+
142+
function outzeros(io::IO, cnt::Integer)
143+
for i = 1:(cnt>>8)
144+
print(io, strzeros)
145+
end
146+
print(io, strzeros[1:(cnt&255)])
147+
end
148+
149+
# Get decimal() argument from scientific notation
150+
function scinote(str::AbstractString)
151+
s = (str[1] == '-') ? "-" : ""
152+
n, expo = split(str, 'e')
153+
n = split(n, '.')
154+
if s == "-"
155+
n[1] = n[1][2:end]
156+
end
157+
if parse(Int64, expo) > 0
158+
shift = parse(Int64, expo) - ((length(n) == 2) ? length(n[2]) : 0)
159+
s * join(n) * repeat("0", shift)
160+
else
161+
shift = -parse(Int64, expo) - ((length(n) == 2) ? length(n[1]) : length(n))
162+
s * "0." * repeat("0", shift) * join(n)
163+
end
164+
end
165+
166+
# Convert a decimal to a string
167+
function Base.print(io::IO, x::Dec)
168+
x.sgn && print(io, '-')
169+
if x.pow < 0
170+
c = string(x.val)
171+
shift = x.pow + length(c)
172+
if shift > 0
173+
print(io, c[1:shift], '.', c[(shift+1):end])
174+
else
175+
print(io, "0.")
176+
outzeros(io, -shift)
177+
print(io, c)
178+
end
179+
else
180+
print(io, x.val)
181+
x.pow > 0 && outzeros(io, x.pow)
182+
end
183+
end
184+
185+
# Zero/one value
186+
Base.zero(::Type{Dec}) = Dec(0, 0, 0)
187+
Base.one(::Type{Dec}) = Dec(0, 0, 1)
188+
189+
# convert a decimal to any subtype of Real
190+
(::Type{T})(x::Dec) where {T<:Real} = parse(T, string(x))
191+
192+
# Convert a decimal to an integer if possible, a float if not
193+
function number(x::Dec)
194+
ix = (str = string(x) ; fx = parse(Float64, str); round(Int64, fx))
195+
(ix == fx) ? ix : fx
196+
end
197+
198+
# sign
199+
Base.signbit(x::Dec) = x.sgn
200+
201+
# Equality
202+
203+
Base.iszero(x::Dec) = iszero(x.val)
204+
205+
# equals() now depends on == instead of the other way round.
206+
207+
function ==(x::Dec, y::IntDec)
208+
# Check if both are zero, regardless of sign
209+
iszero(x) && return iszero(y)
210+
iszero(y) && return false
211+
# Make sure signs are the same
212+
_getsign(x) == _getsign(y) || return false
213+
# If scales are the same, don't bother to equalize the scales
214+
(d = _getscale(x) - _getscale(y)) == 0 && return _eq(x, y)
215+
# Find out how much we need to multiply by to equalize the scales
216+
# Note: further optimization would use tables to see if the size (in limbs, or bits) of the two operands
217+
# could possibly be ==, without even doing the (somewhat expensive) scaling operation.
218+
d < 0 ? _eq(x, _scale(y, -d)) : _eq(_scale(x, d), y)
219+
end
220+
221+
==(x::Integer, y::Dec) = y == x
222+
223+
function <(x::Dec, y::IntDec)
224+
# Check for both zeros, regardless of sign
225+
iszero(x) && return ifelse(iszero(y), false, !_getsign(y))
226+
iszero(y) && return _getsign(x)
227+
# Check signs
228+
xsgn = _getsign(x)
229+
ysgn = _getsign(y)
230+
xsgn == ysgn || return xsgn
231+
# If scales are the same, don't bother to equalize the scales
232+
(dscl = _getscale(x) - _getscale(y)) == 0 && return xor(_lt(x, y), ysgn)
233+
# Find out how much we need to multiply by to equalize the scales
234+
# Note: further optimization would use tables to see if the size (in limbs, or bits) of the two operands
235+
# are such that one is definitely larger than the other, without even doing the (somewhat expensive) scaling operation.
236+
xor(dscl < 0 ? _lt(x, _scale(y, -dscl)) : _lt(_scale(x, dscl), y), ysgn)
237+
end
238+
239+
function <(x::Integer, y::Dec)
240+
# Check for both zeros, regardless of sign
241+
iszero(x) && return ifelse(iszero(y), false, !_getsign(y))
242+
iszero(y) && return _getsign(x)
243+
# Check signs
244+
xsgn = _getsign(x)
245+
ysgn = _getsign(y)
246+
xsgn == ysgn || return xsgn
247+
# If scales are the same, don't bother to equalize the scales
248+
xor((dscl = _getscale(y)) == 0 ? _lt(x, y) :
249+
(dscl < 0 ? _lt(_scale(x, -dscl), y) : _lt(x, _scale(y, dscl))), ysgn)
250+
end
251+
252+
# Rounding
253+
function round(x::Dec; digits::Int=0, normal::Bool=false)
254+
shift = BigInt(digits) + x.pow
255+
if !(shift > BigInt(0) || shift < x.pow)
256+
c = Base.round(x.val / BigInt(10)^(-shift))
257+
x = Dec(x.sgn, x.pow - shift, BigInt(c))
258+
end
259+
normal ? x : normalize(x, rounded=true)
260+
end
261+
262+
# Normalization: remove trailing zeros in coefficient
263+
function normalize(x::Dec; rounded::Bool=false)
264+
# Note: this is very inefficient
265+
# First, one can count the trailing zero bits, and that will give an indication
266+
# of the maximum 0 digits (because 10 is 5*2)
267+
p = 0
268+
if x.val != 0
269+
while x.val % BigInt(10)^(p+1) == 0
270+
p += 1
271+
end
272+
end
273+
c, r = divrem(x.val, BigInt(10)^p)
274+
q = (c == 0 && !x.sgn) ? 0 : x.pow + p
275+
v = Dec(x.sgn, q, abs(c))
276+
rounded ? v : round(v, digits=DIGITS, normal=true)
277+
end
278+
279+
end # Decs

0 commit comments

Comments
 (0)