Skip to content

Commit 5ab7fd9

Browse files
committed
don't allocate error strings unless there is an error (fixes #58)
1 parent 135efec commit 5ab7fd9

File tree

2 files changed

+75
-45
lines changed

2 files changed

+75
-45
lines changed

src/DecFP.jl

Lines changed: 74 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,68 @@ const _buffer = fill(0x00, 1024)
2929
import Base.promote_rule
3030
import Base.Grisu.DIGITS
3131

32+
#############################################################################
33+
# exception handling via global flags
34+
# (todo: recompile library with GLOBAL_FLAGS=0 for thread-safety)
35+
36+
const flags = Ref{Ptr{Cuint}}() # set to __bid_IDEC_glbflags in __init__
37+
38+
# clear exception flags and return x
39+
function nox(x)
40+
unsafe_store!(flags[], 0)
41+
return x
42+
end
43+
# Check exception flags in mask & throw, otherwise returning x;
44+
# always clearing exceptions. These are macros so that
45+
# the error message is only evaluated if an exception occurs.
46+
47+
macro xchk(x, args...)
48+
mask=0x3f
49+
if !isempty(args) && Meta.isexpr(args[end], :(=)) && args[end].args[1] == :mask # mask=... keyword at end
50+
mask = esc(args[end].args[2])
51+
args = args[1:end-1]
52+
end
53+
quote
54+
ret = $(esc(x))
55+
f = unsafe_load(flags[])
56+
unsafe_store!(flags[], 0)
57+
f & $mask != 0 && xchk_throw(f, $(map(esc,args)...))
58+
ret
59+
end
60+
end
61+
62+
macro xchk1(x, exc, args...)
63+
mask=0x3f
64+
if !isempty(args) && Meta.isexpr(args[end], :(=)) && args[end].args[1] == :mask # mask=... keyword at end
65+
mask = esc(args[end].args[2])
66+
args = args[1:end-1]
67+
end
68+
quote
69+
ret = $(esc(x))
70+
f = unsafe_load(flags[])
71+
unsafe_store!(flags[], 0)
72+
f & $mask != 0 && throw($exc($(map(esc,args)...)))
73+
ret
74+
end
75+
end
76+
77+
# separate this exception throwing code into a function to avoid
78+
# inlining it over and over in the @xchk macro
79+
function xchk_throw(f, args...)
80+
f & INEXACT != 0 && throw(InexactError(args...))
81+
f & OVERFLOW != 0 && throw(OverflowError(args...))
82+
f & DIVBYZERO != 0 && throw(DivideError())
83+
f & INVALID != 0 && throw(DomainError(args...))
84+
f & UNDERFLOW != 0 && error("underflow")
85+
f & UNNORMAL != 0 && error("unnormal")
86+
@assert false # this should be unreachable
87+
end
88+
89+
#############################################################################
90+
3291
const rounding = Ref{Ptr{Cuint}}()
33-
const flags = Ref{Ptr{Cuint}}()
34-
# rounding modes, from bid_functions.h
3592

93+
# rounding modes, from bid_functions.h
3694
const rounding_c2j = [RoundNearest, RoundDown, RoundUp, RoundToZero, RoundFromZero]
3795
const rounding_j2c = Dict{RoundingMode, UInt32}([(rounding_c2j[i], Cuint(i-1)) for i in 1:length(rounding_c2j)])
3896

@@ -109,7 +167,7 @@ for w in (32,64,128)
109167
if isnan(x) && !isnanstr(s)
110168
throw(ArgumentError("invalid number format $s"))
111169
end
112-
return xchk(x, InexactError, :parse, $BID, s)
170+
return @xchk1(x, InexactError, :parse, $BID, s)
113171
end
114172

115173
$BID(x::AbstractString) = parse($BID, x)
@@ -175,7 +233,7 @@ for w in (32,64,128)
175233
n = length(DIGITS) - 1
176234
end
177235
# rounded = round(x * exp10($BID(n)), RoundNearestTiesAway)
178-
rounded = xchk(ccall(($(bidsym(w,"round_integral_nearest_away")), libbid), $BID, ($BID,), x * exp10($BID(n))), InexactError, :round, $BID, x, mask=INVALID | OVERFLOW)
236+
rounded = @xchk1(ccall(($(bidsym(w,"round_integral_nearest_away")), libbid), $BID, ($BID,), x * exp10($BID(n))), InexactError, :round, $BID, x, mask=INVALID | OVERFLOW)
179237
if rounded == 0
180238
DIGITS[1] = UInt8('0')
181239
return Int32(1), Int32(1), signbit(x)
@@ -224,7 +282,7 @@ for w in (32,64,128)
224282
end
225283
normalized_exponent = nox(ccall(($(bidsym(w,"ilogb")), libbid), Cint, ($BID,), x))
226284
# rounded = round(x * exp10($BID(n - 1 - normalized_exponent)), RoundNearestTiesAway)
227-
rounded = xchk(ccall(($(bidsym(w,"round_integral_nearest_away")), libbid), $BID, ($BID,), x * exp10($BID(n - 1 - normalized_exponent))), InexactError, :round, $BID, x, mask=INVALID | OVERFLOW)
285+
rounded = @xchk(ccall(($(bidsym(w,"round_integral_nearest_away")), libbid), $BID, ($BID,), x * exp10($BID(n - 1 - normalized_exponent))), InexactError, :round, $BID, x, mask=INVALID | OVERFLOW)
228286
rounded_exponent = nox(ccall(($(bidsym(w,"ilogb")), libbid), Cint, ($BID,), rounded))
229287
ccall(($(bidsym(w,"to_string")), libbid), Cvoid, (Ptr{UInt8}, $BID), _buffer, rounded)
230288
i = 2
@@ -248,7 +306,7 @@ for w in (32,64,128)
248306

249307
Base.nextfloat(x::$BID) = nox(_nextfloat(x))
250308
Base.prevfloat(x::$BID) = nox(_prevfloat(x))
251-
Base.eps(x::$BID) = ifelse(isfinite(x), xchk(nextfloat(x) - x, OVERFLOW, "$($BID) value overflow"), $(_parse(T, "NaN")))
309+
Base.eps(x::$BID) = ifelse(isfinite(x), @xchk(nextfloat(x) - x, "$($BID) value overflow", mask=OVERFLOW), $(_parse(T, "NaN")))
252310

253311
# the meaning of the exponent is different than for binary FP: it is 10^n, not 2^n:
254312
# Base.exponent(x::$BID) = nox(ccall(($(bidsym(w,"ilogb")), libbid), Cint, ($BID,), x))
@@ -264,11 +322,11 @@ for w in (32,64,128)
264322
end
265323

266324
for f in (:exp,:log,:sin,:cos,:tan,:asin,:acos,:atan,:sinh,:cosh,:tanh,:asinh,:acosh,:atanh,:log1p,:expm1,:log10,:log2,:exp2,:exp10,:lgamma,:sqrt,:cbrt,:abs)
267-
@eval Base.$f(x::$BID) = xchk(ccall(($(bidsym(w,f)), libbid), $BID, ($BID,), x), "invalid operation '$($f)' on $($BID)", mask=INVALID)
325+
@eval Base.$f(x::$BID) = @xchk(ccall(($(bidsym(w,f)), libbid), $BID, ($BID,), x), "invalid operation '$($f)' on $($BID)", mask=INVALID)
268326
end
269327

270328
for (f,c) in ((:gamma,"tgamma"), (:-,"negate"), (:round,"nearbyint"))
271-
@eval Base.$f(x::$BID) = xchk(ccall(($(bidsym(w,c)), libbid), $BID, ($BID,), x), "invalid operation '$($c)' on $($BID)", mask=INVALID)
329+
@eval Base.$f(x::$BID) = @xchk(ccall(($(bidsym(w,c)), libbid), $BID, ($BID,), x), "invalid operation '$($c)' on $($BID)", mask=INVALID)
272330
end
273331

274332
for (f,c) in ((:(==),"quiet_equal"), (:>,"quiet_greater"), (:<,"quiet_less"), (:(>=), "quiet_greater_equal"), (:(<=), "quiet_less_equal"))
@@ -300,7 +358,7 @@ for w in (32,64,128)
300358
@eval promote_rule(::Type{$BID}, ::Type{$BID′}) = $BID
301359
end
302360
if w != w′
303-
@eval Base.convert(::Type{$BID}, x::$BID′) = xchk(ccall(($(string("__bid",w′,"_to_","bid",w)), libbid), $BID, ($BID′,), x), INEXACT, :convert, $BID, x)
361+
@eval Base.convert(::Type{$BID}, x::$BID′) = @xchk1(ccall(($(string("__bid",w′,"_to_","bid",w)), libbid), $BID, ($BID′,), x), InexactError, :convert, $BID, x, mask=INEXACT)
304362
end
305363

306364
# promote binary*decimal -> decimal, for consistency with other operations above
@@ -321,12 +379,12 @@ for w in (32,64,128)
321379
for (i′, i′str) in (("Int$w′", "int$w′"), ("UInt$w′", "uint$w′"))
322380
Ti′ = eval(Symbol(i′))
323381
@eval begin
324-
Base.trunc(::Type{$Ti′}, x::$BID) = xchk(ccall(($(bidsym(w,"to_",i′str,"_xint")), libbid), $Ti′, ($BID,), x), InexactError, :trunc, $BID, x, mask=INVALID | OVERFLOW)
325-
Base.floor(::Type{$Ti′}, x::$BID) = xchk(ccall(($(bidsym(w,"to_",i′str,"_xfloor")), libbid), $Ti′, ($BID,), x), InexactError, :floor, $BID, x, mask=INVALID | OVERFLOW)
326-
Base.ceil(::Type{$Ti′}, x::$BID) = xchk(ccall(($(bidsym(w,"to_",i′str,"_xceil")), libbid), $Ti′, ($BID,), x), InexactError, :ceil, $BID, x, mask=INVALID | OVERFLOW)
327-
Base.round(::Type{$Ti′}, x::$BID) = xchk(ccall(($(bidsym(w,"to_",i′str,"_xrnint")), libbid), $Ti′, ($BID,), x), InexactError, :round, $BID, x, mask=INVALID | OVERFLOW)
328-
Base.round(::Type{$Ti′}, x::$BID, ::RoundingMode{:NearestTiesAway}) = xchk(ccall(($(bidsym(w,"to_",i′str,"_xrninta")), libbid), $Ti′, ($BID,), x), InexactError, :round, $BID, x, mask=INVALID | OVERFLOW)
329-
Base.convert(::Type{$Ti′}, x::$BID) = xchk(ccall(($(bidsym(w,"to_",i′str,"_xfloor")), libbid), $Ti′, ($BID,), x), InexactError, :convert, $BID, x)
382+
Base.trunc(::Type{$Ti′}, x::$BID) = @xchk1(ccall(($(bidsym(w,"to_",i′str,"_xint")), libbid), $Ti′, ($BID,), x), InexactError, :trunc, $BID, x, mask=INVALID | OVERFLOW)
383+
Base.floor(::Type{$Ti′}, x::$BID) = @xchk1(ccall(($(bidsym(w,"to_",i′str,"_xfloor")), libbid), $Ti′, ($BID,), x), InexactError, :floor, $BID, x, mask=INVALID | OVERFLOW)
384+
Base.ceil(::Type{$Ti′}, x::$BID) = @xchk1(ccall(($(bidsym(w,"to_",i′str,"_xceil")), libbid), $Ti′, ($BID,), x), InexactError, :ceil, $BID, x, mask=INVALID | OVERFLOW)
385+
Base.round(::Type{$Ti′}, x::$BID) = @xchk1(ccall(($(bidsym(w,"to_",i′str,"_xrnint")), libbid), $Ti′, ($BID,), x), InexactError, :round, $BID, x, mask=INVALID | OVERFLOW)
386+
Base.round(::Type{$Ti′}, x::$BID, ::RoundingMode{:NearestTiesAway}) = @xchk1(ccall(($(bidsym(w,"to_",i′str,"_xrninta")), libbid), $Ti′, ($BID,), x), InexactError, :round, $BID, x, mask=INVALID | OVERFLOW)
387+
Base.convert(::Type{$Ti′}, x::$BID) = @xchk1(ccall(($(bidsym(w,"to_",i′str,"_xfloor")), libbid), $Ti′, ($BID,), x), InexactError, :convert, $BID, x)
330388
Base.$(Symbol("$Ti′"))(x::$BID) = convert($Ti′, x)
331389
end
332390
end
@@ -405,35 +463,7 @@ macro d32_str(s, flags...) parse(Dec32, s) end
405463
macro d64_str(s, flags...) parse(Dec64, s) end
406464
macro d128_str(s, flags...) parse(Dec128, s) end
407465

408-
# clear exception flags and return x
409-
function nox(x)
410-
unsafe_store!(flags[], 0)
411-
return x
412-
end
413-
414-
# check exception flags in mask & throw, otherwise returning x;
415-
# always clearing exceptions
416-
function xchk(x, args...; mask::Integer=0x3f)
417-
f = unsafe_load(flags[])
418-
unsafe_store!(flags[], 0)
419-
if f & mask != 0
420-
f & INEXACT != 0 && throw(InexactError(args...))
421-
f & OVERFLOW != 0 && throw(OverflowError(args...))
422-
f & DIVBYZERO != 0 && throw(DivideError())
423-
f & INVALID != 0 && throw(DomainError(args...))
424-
f & UNDERFLOW != 0 && error("underflow")
425-
f & UNNORMAL != 0 && error("unnormal")
426-
end
427-
return x
428-
end
429-
430-
function xchk(x, exc::Type{E}, args...; mask::Integer=0x3f) where {E<:Exception}
431-
f = unsafe_load(flags[])
432-
unsafe_store!(flags[], 0)
433-
f & mask != 0 && throw(exc(args...))
434-
return x
435-
end
436-
466+
# for zero-padding in printing routines above
437467
function writezeros(io::IO, n::Int)
438468
for i = 1:n
439469
write(io, UInt8('0'))

test/runtests.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import DecFP.isnanstr
1010
@test isnanstr("nan") && isnanstr(" +NAN") && isnanstr("-NaN") && !isnanstr("nano")
1111

1212
for T in (Dec32, Dec64, Dec128)
13-
println(STDERR, "TESTING $T ...")
13+
info("TESTING $T ...")
1414

1515
if T == Dec32
1616
@test d32"3.2" * d32"4.5" == d32"14.4"

0 commit comments

Comments
 (0)