Binary Literals

This may be a silly trivial question but I can not find the answer anywhere.
When I write 1.e10 (in 'scientific' notation), it means decimal literal 10^10.
Is there any equivalent shorthand way in Rust to specify, say 2^53 ?
That is, where the assumed exponentiation base is 2 instead of 10?

1 << 53

4 Likes

I know but I am after a constant that needs no evaluation, that rules out the obvious: 2^53 and 1 << 53, etc, all of which have to be calculated.
The only 'constant' way I am aware of is: 100000000000000000...53 zeroes, which, you will agree, is rather cumbersome.

1 << 53 is a constant in every sense that matters; you can even use it in a const:

const X: u64 = 1 << 53;

How do you foresee 1 << 53 causing problems for your use-case? If you're worried about a run-time cost, there isn't any since this kind of expression will be constant-folded even in debug builds.

11 Likes

I guess I might have to settle for that.

Note that there's no need to worry about such things any more. If you mean 4 * x, these day you should just write that, and the compiler will reliably translate it into a shift or addition or whatever, far better than you could by hand.

And even if you write out floating-point calls, the compiler will still just put the constant in there, not calculate it at runtime:

4 Likes

For this reason, bit-twiddling constants are usually specified in hexadecimal and with appropriate separators. 253 = 0x20_0000_0000_0000.

7 Likes

Oh, leave it up to the compiler, you say, but in your own example, it gives two different answers:
0x4340_0000_0000_0000 and
....0x20_0000_0000_0000
I know, the first one is probably the floating point representation, with the extra complement of exponent and sign and what not but still, it is not exactly the same constant then, is it?

It's an exactly same constant when we treat it as typed value. Of course the bit representation is different.

It's somewhat unfortunate that Rust doesn't have binary and hex floating point literals, and that floats don't implement hex formatting and parsing (LowerHex), for cases where you want to write a float with full precision.

The approach of "write it in decimal with enough digits for the rounding to not matter" is an ugly hack.

1 Like

It is not clear how different people would expect hexadecimal float literals to behave. There are several possible interpretations, including at least "hex of the bit representation" and "base-16 number before and after the hexadecimal point". I don't think either of those is more obvious.

If you want the bitwise interpretation, you can already transmute a u32 to f32 (and likewise with 64-bit values). This works in const and static declarations. Once transmute also works in const fn bodies, fxx::from_bits() will also become a const fn itself, alleviating the need for language-level hex literals.

1 Like

For transmuting bit representations there is also f32::from_bits. Edit: already mentioned above.

But what I meant was analogous to decimal notation, just in hex.

C++ has this, e.g. 0x1.a0p-3 means (1 + 10/16) * 2^(-3).

2 Likes

At first glance, I would have expected this to be (1 + 10/16) * 16^(-3) instead, as that's the base you're using.

4 Likes

Yes, I was surprised by that too. Also the exponent is in decimal which is a bit surprising, so the literal 0x1p+53 would represent the number that OP wanted.

This notation is actually part of the IEEE-754 standard.

1 Like

I had to use different types for each example (and thus have different bit patterns for the floats) because if I don't, then the compiler notices how identical they are and doesn't even bother emitting all the different functions:

While I think it is pretty clear that in this particular case it probably wouldn't be the best option, I think this thread should at least mention the existence of binary literals, (like 0b1010 == 10), since they can be useful in certain cases. Say, for bit twiddling constants that are only one or two bytes long.

fn main() {
    const X: u64 = 0b0000_0000_0010_0000__0000_0000_0000_0000__0000_0000_0000_0000__0000_0000_0000_0000;
    
    dbg!(X, X == 1 << 53);
}

Playground link

2 Likes

from_bits() sounds good but why not have it dynamic? After all, it is just a number, it is hardly going to break any memory or cause any other unspeakable damage. Valid numerical values should not be a priori ruled out just because they may involve tricky manual conversion.

I often think that Rust is perhaps taking its paternalistic attitude a bit too far. In any case, you can never entirely legislate against calculating unintentionally wrong numbers.

Downcasting, say, u64 down to f64, defined exactly as I want it, is a fairly useful application case, I would say. And not just for constants.

That is hideous. I would refer to see (1 + 10/16) * 2^(-3)

2 Likes

Well, you can always write it out as a compile time calculation if you prefer, as has been pointed out, but my original question was more about the (unavailability) of the notation, that is the expressiveness of the language.

By the way, is that not actually wrong in Rust, ^ being (confusingly) the XOR operation?
So much for it being safer than the p notation.

This conversation seems to be straying from a "How do I..." discussion into a language design discussion, which more properly belongs on IRLO. Note that there's a high bar to clear for any language changes, as they both risk breaking existing programs and make a future commitment to support those changes, so you'll need to have a clear argument for how your proposed change is better, or at least no worse, for most Rust programmers.

2 Likes