Solidity Math – Why Wrap `uint()` Around Number When Dividing with Remainder?

mathsolidityuint256

Why do you have to wrap a uint() around a number when dividing it and there is a reminder?

When I do as the code shows below I get no error and the code works fine.

function doMath() public pure returns(uint256){
    return 100/2;
}

But when I do as the code shows below I get an error:

return type rational_const is not explicitly convertible to type…

function doMath() public pure returns(uint256){
    return 100/3;
}

I know the error will go away if I wrap both uint(100) and uint(3) like so. But my question is why do I need to wrap them around uint() for the compiler not to throw an error?

Best Answer

That's a very good question. It touches upon a detail that is probably not very well known, even though it's documented (Rational and Integer Literals):

Number literal expressions retain arbitrary precision until they are converted to a non-literal type (i.e. by using them together with a non-literal expression or by explicit conversion). This means that computations do not overflow and divisions do not truncate in number literal expressions.

For example, (2**800 + 1) - 2**800 results in the constant 1 (of type uint8) although intermediate results would not even fit the machine word size. Furthermore, .5 * 8 results in the integer 4 (although non-integers were used in between).

In other words, literal numbers like 100 and 3 are not uints or any other integer type. Instead Solidity has a special internal type for them and expressions using only literals are evaluated at compilation time and with arbitrary precision. Only when you assign them or use them in an expression with a non-literal they get converted to an actual integer type.

Another important fact is that you can mix integer and rational literals and still retain these properties. So 100/3 is still of a literal type while uint(100)/3, 100/uint(3) or uint(100)/uint(3) all force a conversion to uint. For uint, operator / is the integer division that silently discards the fractional part.

So the result of uint(100)/uint(3) is already uint and you can return it without extra conversion. 100/3 is of the literal type and the compiler does not silently truncate literals. It instead requires you to do an explicit conversion to uint (i.e. uint(100/3)) to signal that it's really what you want to happen. It's not needed with 100/2 because the result is an integer literal that does not require truncation and the compiler can implicitly convert it to uint.

Related Topic