Printing of unsigned numbers

I’ve added the pirate line to my startup.jl file and now they all look more familiar to my eyes :slightly_smiling_face:

But I still find strange things around the unsigneds. Why print prints scalars in decimal and arrays in hexa?

julia> a = rand(UInt8, 2,2)
2×2 Matrix{UInt8}:
 0xf7  0xeb
 0x02  0x8c

julia> print(a[1])
247
julia> print(a)
UInt8[0xf7 0xeb; 0x02 0x8c]
2 Likes

This is a fun one, reminds me of the differences and inconsistencies between str and repr in python:

julia> a = rand(UInt8, 2,2)
2×2 Matrix{UInt8}:
 0x41  0x91
 0x63  0x71

julia> a[1]
0x41

julia> print(a[1])
65
julia> repr(a[1])
"0x41"

Back in 2015, it was decided to make print use decimal for unsigned values: unsigned integers interpolate in hex format · Issue #3450 · JuliaLang/julia · GitHub

However, print of an array calls the fallback method of print that calls show. Probably the reasoning is similar to this discussion of tuple printing (which also uses show), where @jeff.bezanson wrote:

The reason we do this is that numbers have canonical printed representations, but tuples do not. As soon as we’re printing ( ,) , we’re using julia syntax and might as well stick with it. If we printed (17185,) , that would be a representation of a julia object, but of a different julia object, which seems like a strange thing to do.

1 Like

Your view has merit, and of course there are (rare) cases where the “can’t be negative” allows for certain performance optimizations:

julia> shift(x::Integer, n::Integer) = x << n
shift (generic function with 1 method)

julia> @code_typed shift(5, 3)
CodeInfo(
1 ─ %1 = Base.sle_int(0, n)::Bool
│   %2 = Base.bitcast(UInt64, n)::UInt64
│   %3 = Base.shl_int(x, %2)::Int64
│   %4 = Base.neg_int(n)::Int64
│   %5 = Base.bitcast(UInt64, %4)::UInt64
│   %6 = Base.ashr_int(x, %5)::Int64
│   %7 = Base.ifelse(%1, %3, %6)::Int64
└──      return %7
) => Int64

julia> @code_typed shift(5, 0x03)
CodeInfo(
1 ─ %1 = Base.shl_int(x, n)::Int64
└──      return %1
) => Int64

But there are also arguments against them; one description is https://www.learncpp.com/cpp-tutorial/unsigned-integers-and-why-to-avoid-them/

9 Likes

This won’t make for a good first post, but this discussion genuinely feels a bit odd to me, including the Github discussion. I’m not questioning anyone’s needs, wants or knowledge, but wanting a quick look at numerical values - including unsigned ints - in decimal form can not be anomaly, which these threads almost make it sound like.

I’ve been parsing binary formats in other languages lately and decimal values do help there as well (“can not be negative” is also useful). To print in padded hex form in e.g. Rust I’d just do println!("{:02x}", 10_u8); (padded, lowercase hex) otherwise the decimal value is printed - the equivalent of Printf, I guess? And this is a language that among other things targets systems programming, so I’ll presume there are more than a few developers frequently outputting memory addresses and what not. I’ve never seen this kind of discussion in any Rust-forum yet when I try to find out why uints “only” print as hex in Julia, the first hits for both Google and DuckDuckGo returns discussions where users are getting pummelled with reasons and examples as to why they shouldn’t be using uints for this or that, or why there’s little need to see their value in decimal form. No one even mentions that Printf.@printf("%u", 10) is an option (in all fairness my Rust example is a macro as well, it’s just available without imports).

I’m not saying that there’s no merit to the general uint discussion (or that my use of uints is “correct”), and consider me enlightened as to why it’s not necessarily a good choice - Rust picks Int32 as its default int value, I think.

4 Likes

I think this (in part) happens because printing in languages like Rust almost always requires specifying how you want to print something, where you print something. Julia with its REPL has to pick some default, just for displaying returned values in the REPL in the first place.

I don’t know how these sorts of questions are asked in Rust forums, but I’d wager the expectations of users are quite different, with users having more of a C/C++ background compared to the (mostly) R/Python/Matlab background here (all of which just print decimal, I think). I do agree that "Use printf" would be useful to mention at the top, though. In other threads (at least those I recall OTOH) that often then immediately got to “why isn’t that the default”, due to differing expectations…

5 Likes

The thing is, you can’t really have both decimal and hex as default printing, you have to choose one. The discussion is just: which is better? Then it makes sense to advance arguments for ones view.

There are valid reasons to print in either format, but there has to be one default.

4 Likes

That’s a fair point, of course. This was just the first time I’ve seen such a vigorous discussion on this topic, with the final choice being hex to boot! :slight_smile:

One thing I forgot - julia doesn’t have UInt as it’s default integer type, but Int (which is Int64/32 on 64 and 32 bit systems respectively, always signed). Writing unsigned literals has to be done in hex with prefix 0x already, so encountering an unsigned integer in julia is most often a deliberate choice (as was with OP, based on the desire to interface with a third party API). It just happened that in this case the chosen default representation when printing was inconvenient which can just as well happen were the representation different :person_shrugging:

6 Likes

Ah, ok. Rust has isize (signed) and usize (unsigned) which are either 32bit or 64bit depending on the system and also used for indexing. let x = 3 is by default a signed 32bit unless type is specified. (Sorry for all the Rust examples, that’s basically the only statically typed language I know…)

2 Likes