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

Exponentiation producing inconsistent results #4893

Closed
nweller opened this issue Sep 4, 2018 · 6 comments
Closed

Exponentiation producing inconsistent results #4893

nweller opened this issue Sep 4, 2018 · 6 comments

Comments

@nweller
Copy link

nweller commented Sep 4, 2018

Across multiple unsigned integer types I've tried - e.g. uint8 - the exponentiation operator can produce a result which is outside of the range of that type in some contexts, but exhibits the truncation behavior I'd expect in other contexts.

I get different failure modes in the truffle-based test framework I'm using (with ganache-cli, Linux, solc-js 0.4.24 - this produces unexpected values) and http://remix.ethereum.org/ (this produces a VM error).

See:

contract c0 {
  // OK: 0 ** 0 returns 1
  function f1() returns (uint8) { return uint8(0) ** uint8(0); }

  // FAILURE:
  //   - In truffle: returns 0, indicating as per above that
  // "uint8(uint8(2) ** uint8(8))" is not 0 - but it should be as per the uint8 range 
  //   - In Remix: transaction fails due to a VM eror
  function f2() returns (uint8) { return uint8(0) ** uint8(uint8(2) ** uint8(8)); }

  // OK: 2 ** 8 returns the expected zero-truncated result
  function f3() returns (uint8) { return uint8(2) ** uint8(8); }

  // OK: In this context, "uint8(uint8(2) ** uint8(8))" is 0, contradicting
  // the exponentiation context above
  function f4() returns (bool) { return uint8((uint8(2) ** uint8(8))) > 0; }
}

This probably has something to do with this statement from the docs, which indicates that intermediate results exceeding the size of their input operand types can be produced:

https://solidity.readthedocs.io/en/v0.4.24/types.html

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).
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.

If I understand this correctly, the expression "x ** y" is dynamically typed and may produce a result which is outside of the range of x's type. But in that case using an explicit conversion to the smaller type should at least consistently always truncate (preferred, IMO) or never truncate.

(This report is part of an ICE Center@ETH Zurich project on automated compiler validation funded by the Ethereum Foundation.)

@chriseth chriseth added this to Inbox in 0.5.0 via automation Sep 4, 2018
@chriseth
Copy link
Contributor

chriseth commented Sep 4, 2018

This is unrelated to the part of the documentation you cite because due to the type conversions to uint8 we are not in the domain of literal expressions anymore.

I would assume this is due to cleanup not being performed for intermediate values. "Lazy cleanup" can be done as long as cleanup and operation "commute" which is the case for addition and multiplication and I would guess also "almost" for EXP except for the special case of zero.

@ekpyron
Copy link
Member

ekpyron commented Sep 4, 2018

The following returns (0,0,0) in remix...

contract C {
    function f() public pure returns (uint8, uint8, uint8) {
        return (42, uint8(0) ** uint8(uint8(2) ** uint8(8)), 21);
    }
}

@chriseth
Copy link
Contributor

chriseth commented Sep 4, 2018

I would assume that we do not have to clean the base, but we do have to clean the exponent, and the only special case here would be where the exponent is zero (in the respective representation).

@chriseth
Copy link
Contributor

chriseth commented Sep 4, 2018

So basically 0**0 == 1, but 0**0x100 == 0

@chriseth chriseth mentioned this issue Sep 4, 2018
@nweller
Copy link
Author

nweller commented Sep 4, 2018

Thanks for the replies.

Is the following case - which also involves the exponentiation operator - a duplicate, or something else? It behaves differently for me in Remix by producing an unexpected result rather than a VM error.

I construct a value of 1 to use as exponent using the unary minus operator, which results in some invalid value, see:

contract c0 {
    event UINT8(uint8 arg);
    event UINT256(uint256 arg);

  function f() public payable {
      // FAILURE: event result is 0, even though the exponent "uint8(-uint8(0xff))"
      // should be 1 and the base is 2
      emit UINT8(uint8(2) ** (uint8(-uint8(0xff))));
      // OK - 2^1 returns 2
      emit UINT8(uint8(2) ** uint8(1));
      // OK - confirms that "uint8(-uint8(0xff))" yields 1 in isolation
      emit UINT8(uint8(-uint8(0xff)));

      // The following statements show that the problem does not occur for uint256,
      // so it may also only affect small types
      emit UINT256(uint256(2) ** (uint256(-uint256(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff))));
      emit UINT256(uint256(2) ** uint256(1));
      emit UINT256(uint256(-uint256(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)));
  }
}

@chriseth
Copy link
Contributor

chriseth commented Sep 5, 2018

Phew, I almost thought that we have the same problem with unary minus, but this is actually another incarnation of the missing cleanup for exponentiation.

@chriseth chriseth moved this from Inbox to Under review in 0.5.0 Sep 5, 2018
@chriseth chriseth added this to To do in 0.4.25 via automation Sep 10, 2018
@chriseth chriseth moved this from To do to In progress in 0.4.25 Sep 10, 2018
0.5.0 automation moved this from Under review to Done Sep 10, 2018
0.4.25 automation moved this from In progress to Done Sep 10, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
No open projects
0.4.25
  
Done
0.5.0
  
Done
Development

No branches or pull requests

3 participants