Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reimplement show #60

Merged
merged 2 commits into from
Feb 16, 2018
Merged

Reimplement show #60

merged 2 commits into from
Feb 16, 2018

Conversation

jmkuhn
Copy link
Contributor

@jmkuhn jmkuhn commented Feb 14, 2018

Reimplement show without using @sprintf. The motivation for doing this is to show values without rounding.

julia> maxintfloat(Dec128) - 1
9.999999999999999999999999999999999e33

A side effect of the reimplementation is some recently reported display issues also go away

julia> realmin(d64"3.4")
1.0e-398

julia> realmin(Dec64)
1.0e-398

julia> parse(Dec128, "56123")
56123.0

julia> parse(Dec128, "561234")
561234.0

Please keep issue #59 open since I still need to address this in @sprintf

cc @ScottPJones

@jmkuhn
Copy link
Contributor Author

jmkuhn commented Feb 14, 2018

Windows build failures unrelated to this PR.

src/DecFP.jl Outdated
unsafe_write(io, pointer(_buffer, 2), lastnonzeroindex - 1)
write(io, '0'^(normalized_exponent - lastnonzeroindex + 2), ".0")
elseif normalized_exponent == lastnonzeroindex - 2
unsafe_write(io, pointer(_buffer, 2), lastnonzeroindex - 1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should wrap all of the code that does unsafe_write(..., pointer(_buffer, n)) in GC.@preserve _buffer .... in Julia 0.7. (In Julia 0.6 it is Base.@gc_preserve; unfortunately this is not in Compat yet).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GC.@preserve was added with JuliaLang/julia#25616. Base.@gc_preserve was added with JuliaLang/julia#23610 in 0.7.0-DEV.1795. Is there an equivalent for 0.6?

Copy link
Member

@stevengj stevengj Feb 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it can be a no-op in 0.6.

The need to be more cautious about pointer apparently occurred because the gc became more aggressive in 0.7 (JuliaLang/julia#23562), as I understand it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point - I think I've got to look at a lot of my code in Strs.jl for just that issue, because of the more aggressive GC in master.

src/DecFP.jl Outdated
if normalized_exponent == -1
write(io, "0.")
else
write(io, "0.", '0'^(-normalized_exponent - 1))
Copy link
Member

@stevengj stevengj Feb 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this allocates a temporary string. Might be better to define a writezeros(io, n) = for i=1:n; write(io, UInt8('0')); end function

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That might be a bit slow, even some small tables would help a lot, say strings of 0s, 1:9, then strings of 0's, 10:10:90,
then 100:100:1000. The exponent for Dec128 can be -6143..6144, so at most you'd need to write out the 1000 "0" string 6 times, the length 100 "0" once, a 40 "0" once, and a 4 "0" string, but all with no allocation (with a table size of > 6105, but it could be used by other numeric output packages)

src/DecFP.jl Outdated
r = normalized_exponent
while b > 0
q, r = divrem(r, b)
write(io, Char('0' + q))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the call to Char needed? '0' + q should already give a Char, no? Maybe more efficient to do UInt8('0')+(q%UInt8)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, Steven is correct. Converting what is an ASCII character to Char, and then have to have that checked to see how many bytes it might be, and deal with the new representation of Char, would be inefficient. I'd just put write(io, q%UInt8 + 0x30)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

write(io, 0x30 + q%UInt8) would also work just fine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(UInt8('0') will get inlined as 0x30 and is more readable to people who don't have ASCII memorized 😉.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As long as it still gets in-lined as 0x30, I was afraid with the changes to Char, it might not on master.
Note that if you have UInt8('0' + q) instead of UInt8('0') + UInt8(q), it generates positively horrible code since #24999.

On v0.6.2:

julia> f(q) = ('0' + q)%UInt8
f (generic function with 1 method)

julia> @code_native f(0x9)
	.section	__TEXT,__text,regular,pure_instructions
Filename: REPL[4]
	pushq	%rbp
	movq	%rsp, %rbp
Source line: 4
	addb	$48, %dil
Source line: 1
	movl	%edi, %eax
	popq	%rbp
	retq

On v0.7 (built today) (much too long to paste here!):
https://gist.github.com/ScottPJones/8531146104d22a3a46b6777e90968b42

src/DecFP.jl Outdated
@@ -105,6 +97,83 @@ for w in (32,64,128)

$BID(x::AbstractString) = parse($BID, x)

function Base.show(io::IO, x::$BID)
if isnan(x)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might consider this stylistic, but this code is so long. What do you think of shortening it up a bit, i.e.:

isnan(x) && return write(io, "NaN")
isinf(x) && return write(io, signbit(x) ? "-Inf" : "Inf")
x == 0 && return write(io, signbit(x) ? "-0.0" : "0.0")
ccall(($(bidsym(w,"to_string")), libbid), Cvoid, (Ptr{UInt8}, $BID), _buffer, x)
_buffer[1] == '-'%UInt8 && write(io, '-'%UInt8)

5 lines instead of 24, and IMO easier to read.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can appreciate the shorter code but your use of return has me considering something that I had overlooked before. Even in my original PR, show sometimes returns nothing and sometimes returns an Int with no consistent meaning due to a lack of return at the end of the function. Should I add an explicit return while keeping my other returns as is to ensure show always returns nothing?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, instead of && return write(io, ..., you could have && (write(io, ...); return) instead, to make sure it's always returning nothing

src/DecFP.jl Outdated
if normalized_exponent == -1
write(io, "0.")
else
write(io, "0.", '0'^(-normalized_exponent - 1))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That might be a bit slow, even some small tables would help a lot, say strings of 0s, 1:9, then strings of 0's, 10:10:90,
then 100:100:1000. The exponent for Dec128 can be -6143..6144, so at most you'd need to write out the 1000 "0" string 6 times, the length 100 "0" once, a 40 "0" once, and a 4 "0" string, but all with no allocation (with a table size of > 6105, but it could be used by other numeric output packages)

normalized_exponent = -normalized_exponent
end
b_lb = div(normalized_exponent, 10)
b = 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this use a table, or in some way do fewer multiplications?

b *= 10
end
r = normalized_exponent
while b > 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole loop concerns me, it seems to be doing quite a lot of divisions for each digit output.
Can't you start outputting from the end, and then divide by 10, and get the remainder, every time through the loop.

src/DecFP.jl Outdated
# %f
if normalized_exponent >= 0
if normalized_exponent > lastnonzeroindex - 2
unsafe_write(io, pointer(_buffer, 2), lastnonzeroindex - 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this always starting at 2? If it's positive, does the library "to_string" function output a +?

normalized_exponent = nox(ccall(($(bidsym(w,"ilogb")), libbid), Cint, ($BID,), x))
lastdigitindex = Compat.findfirst(equalto(UInt8('E')), _buffer) - 1
lastnonzeroindex = Compat.findlast(!equalto(UInt8('0')), view(_buffer, 1:lastdigitindex))
if -5 < normalized_exponent < 6
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just set a variable here for the pointer to _buffer, i.e. pnt = pointer(buffer)?
pointer(_buffer, 2) would become pnt + 1.

src/DecFP.jl Outdated
unsafe_write(io, pointer(_buffer, 2), lastnonzeroindex - 1)
write(io, '0'^(normalized_exponent - lastnonzeroindex + 2), ".0")
elseif normalized_exponent == lastnonzeroindex - 2
unsafe_write(io, pointer(_buffer, 2), lastnonzeroindex - 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point - I think I've got to look at a lot of my code in Strs.jl for just that issue, because of the more aggressive GC in master.

src/DecFP.jl Outdated
r = normalized_exponent
while b > 0
q, r = divrem(r, b)
write(io, Char('0' + q))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

write(io, 0x30 + q%UInt8) would also work just fine.

@ScottPJones
Copy link
Contributor

Don't mind the detailed review - this is very good that you are doing this! 👍💯

@stevengj
Copy link
Member

Unrelated CI errors.

@stevengj stevengj merged commit 135efec into JuliaMath:master Feb 16, 2018
@jmkuhn jmkuhn deleted the show branch February 16, 2018 16:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants