Hacker News new | past | comments | ask | show | jobs | submit login

>”When Rust does a * b when a and b are both u64 the CPU actually multiplies them to create a 128-bit result and then Rust just throws away the upper 64 bits.”

Is that right? Wouldn’t that answer look like nonsense? I would have guessed it tossed the lower 64bits so it looked like rounding by truncation at least.




That's how floats work, not ints. Unsigned integers more or less always wrap on overflow, which is what you want, generally speaking. Essentially, the operation is carried out modulo the size of the type.

For signed integers, overflow is undefined in C/C++, but generally results in wrapping as well.


We're talking about Rust, not C/C++.


Just to record the difference.

Unsigned and signed overflow in rust are both well defined to do one of two things, panic or wrap. If debug assertions are enabled they are defined to panic. In practice if debug assertions are not enabled they will wrap, but I believe that is technically subject to change (into a panic). It will realistically only change if the hardware gets much better at catching overflows quickly.

A panic is usually like a exception that you are really not expected to ever catch except at thread boundaries. You can turn them into a immediate process exit with a compiler flag.


> I believe that is technically subject to change

Yes, that's correct.


> except at thread boundaries

Also at FFI boundaries. (i.e. a panicky Rust function, supplied to foreign code as a callback)


Yeah, but I interpreted the question as "is that how it should work?", and then it's useful to compare with different languages. My point was that this is standard behaviour, but there's a slight quirk in C when the ints are signed.


FTR Rust's types (like u64) have several methods for different types of multiplication if you want: checked, saturating, wrapping, overflowing

https://doc.rust-lang.org/std/primitive.u64.html#method.chec...


Well semantics for a number that's too big to fit into the result can be debated, so there's no one right answer and all are nonsense in some way or another.

You talk about rounding but if the number's too big then then you're only ever going to round to the max number you could use, so we might as well call that saturation.

Discarding the upper bits means you are implementing modular arithmetic.

Both saturation and modular arithmetic are well defined, and can both be reasonable choices. I think most languages implement modular arithmetic, so that's what most programmers expect, and it seems a reasonable choice to me compared to saturation.


If you just keep the higher 64 bits, what kind of sense does it make ? We're talking about integer here, not float.

At least, keeping the lower 64 bits is the usual overflow behavior and if the result of the multiplication fits in 64 bits it just works fine.


There are cases for keeping just the high bits.

For example, many language runtime libraries have pseudo random number generators (PRNGs) use a 32-bit linear congruential generator (LCG). Suppose you want to create a rand(n) function which returns an integer between 0 and n-1. One good way of doing this is multiplying the lcg_output (a 32-bit unsigned integer) by n, and returning the high 32 bits.

It's better to do this than a modulus operation because the low bits on LCG sequences are highly predictable.

(This is how the Delphi Random(n) function works.)


By the way, it's only on x86-64 that the CPU's multiplication instruction always returns both the upper and lower 64 bits (in two different registers).

On, say, ARM64, there are two separate instructions: MUL is the normal one that takes two 64-bit inputs and produces one 64-bit output (the lower half of the product), while UMULH ("Unsigned Multiply High") performs the same multiplication but gives you only the upper half. This way, in the common case where you don't care about the upper half, the CPU doesn't have to waste time calculating it.


I wouldn't say that it's only x86. MIPS does the same thing, but doesn't even stick the result in the GPRs, you issue a multiply, and the result ends up in the hi and lo registers that you have to mfhi and mflo to move back into GPRs.


Oh, good point, I've seen that before but forgot about it. Though to be fair, that's 32x32->64, not 64x64->128 :)


The combined HI and LO are 64x64->128 on Mips64. You just have to use dmult instead of mult.


Huh? IMUL is perfectly happy to give you just the low-half of the product.


It doesn’t make sense to throw away either. Remember the 64 bit numbers can be small too. So throwing away the lower will result in 2*2=0.


It's right. This is all integer arithmetic, so you wouldn't want to just drop the lower 64bits.


I think it's correct as stated. Usually machine integer arithmetic is performed modulo 2^bits.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: