mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Fixed point types.
This commit is contained in:
parent
70cbd5d8fb
commit
f91e512f02
@ -553,8 +553,6 @@ BoolResult IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const
|
||||
return (!isSigned() && (numBits() == fixedBytesType->numBytes() * 8));
|
||||
else if (dynamic_cast<EnumType const*>(&_convertTo))
|
||||
return true;
|
||||
else if (auto fixedPointType = dynamic_cast<FixedPointType const*>(&_convertTo))
|
||||
return (isSigned() == fixedPointType->isSigned()) && (numBits() == fixedPointType->numBits());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -893,6 +893,7 @@ void CompilerUtils::convertType(
|
||||
unsigned fractionalDigitsOnStack = 0;
|
||||
if (auto const* typeOnStack = dynamic_cast<IntegerType const*>(&_typeOnStack))
|
||||
{
|
||||
// TODO do we have to subtract the fractional digits?
|
||||
if (targetFixedPointType.numBits() > typeOnStack->numBits())
|
||||
cleanHigherOrderBits(*typeOnStack);
|
||||
}
|
||||
|
@ -434,6 +434,7 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation)
|
||||
case Token::Inc: // ++ (pre- or postfix)
|
||||
case Token::Dec: // -- (pre- or postfix)
|
||||
solAssert(!!m_currentLValue, "LValue not retrieved.");
|
||||
// TODO
|
||||
solUnimplementedAssert(
|
||||
type.category() != Type::Category::FixedPoint,
|
||||
"Not yet implemented - FixedPointType."
|
||||
@ -2265,7 +2266,30 @@ void ExpressionCompiler::appendOrdinaryBinaryOperatorCode(Token _operator, Type
|
||||
void ExpressionCompiler::appendArithmeticOperatorCode(Token _operator, Type const& _type)
|
||||
{
|
||||
if (_type.category() == Type::Category::FixedPoint)
|
||||
solUnimplemented("Not yet implemented - FixedPointType.");
|
||||
{
|
||||
bool checked = (m_context.arithmetic() == Arithmetic::Checked);
|
||||
FixedPointType const& type = dynamic_cast<FixedPointType const&>(_type);
|
||||
string functionName;
|
||||
switch (_operator)
|
||||
{
|
||||
case Token::Add:
|
||||
functionName = m_context.utilFunctions().fixedAddFunction(type, checked);
|
||||
break;
|
||||
case Token::Sub:
|
||||
functionName = m_context.utilFunctions().fixedSubFunction(type, checked);
|
||||
break;
|
||||
case Token::Mul:
|
||||
functionName = m_context.utilFunctions().fixedMulFunction(type, checked);
|
||||
break;
|
||||
case Token::Div:
|
||||
functionName = m_context.utilFunctions().fixedDivFunction(type, checked);
|
||||
break;
|
||||
default:
|
||||
solAssert(false, "Unknown arithmetic operator.");
|
||||
}
|
||||
m_context.callYulFunction(functionName, 2, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
|
||||
if (m_context.arithmetic() == Arithmetic::Checked)
|
||||
|
@ -1139,6 +1139,108 @@ string YulUtilFunctions::wrappingIntExpFunction(
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::fixedAddFunction(FixedPointType const& _type, bool _checked)
|
||||
{
|
||||
return
|
||||
_checked ?
|
||||
overflowCheckedIntAddFunction(*_type.asIntegerType()) :
|
||||
wrappingIntAddFunction(*_type.asIntegerType());
|
||||
}
|
||||
|
||||
string YulUtilFunctions::fixedSubFunction(FixedPointType const& _type, bool _checked)
|
||||
{
|
||||
return
|
||||
_checked ?
|
||||
overflowCheckedIntSubFunction(*_type.asIntegerType()) :
|
||||
wrappingIntSubFunction(*_type.asIntegerType());
|
||||
}
|
||||
|
||||
string YulUtilFunctions::fixedMulFunction(FixedPointType const& _type, bool _checked)
|
||||
{
|
||||
// TODO how does truncation behave for negative numbers?
|
||||
solUnimplementedAssert(_type.numBits() <= 128, "Multiplication only implemented for up to 128 bits.");
|
||||
|
||||
// TODO This is exactly the same as integer mul, but with maxValue being
|
||||
// maxValue * mulitplier and re-scaling at the end
|
||||
// This only works for up to 128 bits.
|
||||
string functionName = "fixed_mul_" + _type.identifier() + (_checked ? "_checked" : "");
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
u256 multiplier = pow(u256(10), _type.fractionalDigits());
|
||||
return
|
||||
// Multiplication by zero could be treated separately and directly return zero.
|
||||
Whiskers(R"(
|
||||
function <functionName>(x, y) -> product {
|
||||
x := <cleanupFunction>(x)
|
||||
y := <cleanupFunction>(y)
|
||||
<?signed>
|
||||
// overflow, if x > 0, y > 0 and x > (maxValue / y)
|
||||
if and(and(sgt(x, 0), sgt(y, 0)), gt(x, div(<maxValue>, y))) { <panic>() }
|
||||
// underflow, if x > 0, y < 0 and y < (minValue / x)
|
||||
if and(and(sgt(x, 0), slt(y, 0)), slt(y, sdiv(<minValue>, x))) { <panic>() }
|
||||
// underflow, if x < 0, y > 0 and x < (minValue / y)
|
||||
if and(and(slt(x, 0), sgt(y, 0)), slt(x, sdiv(<minValue>, y))) { <panic>() }
|
||||
// overflow, if x < 0, y < 0 and x < (maxValue / y)
|
||||
if and(and(slt(x, 0), slt(y, 0)), slt(x, sdiv(<maxValue>, y))) { <panic>() }
|
||||
<!signed>
|
||||
// overflow, if x != 0 and y > (maxValue / x)
|
||||
if and(iszero(iszero(x)), gt(y, div(<maxValue>, x))) { <panic>() }
|
||||
</signed>
|
||||
product := mul(x, y)
|
||||
product := <?signed>sdiv<!signed>div</signed>(product, <multiplier>)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("signed", _type.isSigned())
|
||||
("maxValue", formatNumber(u256(_type.asIntegerType()->maxValue()) * multiplier))
|
||||
("minValue", formatNumber(u256(_type.asIntegerType()->minValue()) * multiplier))
|
||||
("cleanupFunction", cleanupFunction(_type))
|
||||
("panic", panicFunction(PanicCode::UnderOverflow))
|
||||
("multiplier", formatNumber(multiplier))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::fixedDivFunction(FixedPointType const& _type, bool _checked)
|
||||
{
|
||||
// TODO how does truncation behave for negative numbers?
|
||||
solUnimplementedAssert(_type.numBits() <= 128, "Division only implemented for up to 128 bits.");
|
||||
|
||||
// TODO this is similar to int div in the same way as fixed mul is similar to int mul.
|
||||
// TODO There more overflow cases than just `<min> / -1`
|
||||
string functionName = "fixed_div_" + _type.identifier() + (_checked ? "_checked" : "");
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
u256 multiplier = pow(u256(10), _type.fractionalDigits());
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(x, y) -> r {
|
||||
x := <cleanupFunction>(x)
|
||||
y := <cleanupFunction>(y)
|
||||
if iszero(y) { <panicDivZero>() }
|
||||
<?signed>
|
||||
<?checked>
|
||||
// overflow for minVal / -1
|
||||
if and(
|
||||
eq(x, <minVal>),
|
||||
eq(y, mul(sub(0, 1), <multiplier>))
|
||||
) { <panicOverflow>() }
|
||||
</checked>
|
||||
</signed>
|
||||
r := <?signed>sdiv<!signed>div</signed>(mul(x, <multiplier>), y)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("signed", _type.isSigned())
|
||||
("checked", _checked)
|
||||
("minValue", formatNumber(u256(_type.asIntegerType()->minValue())))
|
||||
("cleanupFunction", cleanupFunction(_type))
|
||||
("panicDivZero", panicFunction(PanicCode::DivisionByZero))
|
||||
("panicOverflow", panicFunction(PanicCode::UnderOverflow))
|
||||
("multiplier", formatNumber(multiplier))
|
||||
.render();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
|
||||
{
|
||||
string functionName = "array_length_" + _type.identifier();
|
||||
|
@ -202,6 +202,18 @@ public:
|
||||
/// signature: (base, exponent) -> power
|
||||
std::string wrappingIntExpFunction(IntegerType const& _type, IntegerType const& _exponentType);
|
||||
|
||||
/// signature: (x, y) -> sum
|
||||
std::string fixedAddFunction(FixedPointType const& _type, bool _checked);
|
||||
|
||||
/// signature: (x, y) -> difference
|
||||
std::string fixedSubFunction(FixedPointType const& _type, bool _checked);
|
||||
|
||||
/// signature: (x, y) -> product
|
||||
std::string fixedMulFunction(FixedPointType const& _type, bool _checked);
|
||||
|
||||
/// signature: (x, y) -> quotient
|
||||
std::string fixedDivFunction(FixedPointType const& _type, bool _checked);
|
||||
|
||||
/// @returns the name of a function that fetches the length of the given
|
||||
/// array
|
||||
/// signature: (array) -> length
|
||||
|
@ -797,6 +797,8 @@ bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp)
|
||||
bool isSigned = false;
|
||||
if (auto type = dynamic_cast<IntegerType const*>(commonType))
|
||||
isSigned = type->isSigned();
|
||||
else if (auto type = dynamic_cast<FixedPointType const*>(commonType))
|
||||
isSigned = type->isSigned();
|
||||
|
||||
string args =
|
||||
expressionAsType(_binOp.leftExpression(), *commonType, true) +
|
||||
@ -2726,32 +2728,52 @@ string IRGeneratorForStatements::binaryOperation(
|
||||
}
|
||||
else if (TokenTraits::isArithmeticOp(_operator))
|
||||
{
|
||||
solUnimplementedAssert(
|
||||
_type.category() != Type::Category::FixedPoint,
|
||||
"Not yet implemented - FixedPointType."
|
||||
);
|
||||
IntegerType const* type = dynamic_cast<IntegerType const*>(&_type);
|
||||
solAssert(type, "");
|
||||
bool checked = m_context.arithmetic() == Arithmetic::Checked;
|
||||
switch (_operator)
|
||||
if (_type.category() == Type::Category::Integer)
|
||||
{
|
||||
case Token::Add:
|
||||
fun = checked ? m_utils.overflowCheckedIntAddFunction(*type) : m_utils.wrappingIntAddFunction(*type);
|
||||
break;
|
||||
case Token::Sub:
|
||||
fun = checked ? m_utils.overflowCheckedIntSubFunction(*type) : m_utils.wrappingIntSubFunction(*type);
|
||||
break;
|
||||
case Token::Mul:
|
||||
fun = checked ? m_utils.overflowCheckedIntMulFunction(*type) : m_utils.wrappingIntMulFunction(*type);
|
||||
break;
|
||||
case Token::Div:
|
||||
fun = checked ? m_utils.overflowCheckedIntDivFunction(*type) : m_utils.wrappingIntDivFunction(*type);
|
||||
break;
|
||||
case Token::Mod:
|
||||
fun = m_utils.intModFunction(*type);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
|
||||
switch (_operator)
|
||||
{
|
||||
case Token::Add:
|
||||
fun = checked ? m_utils.overflowCheckedIntAddFunction(type) : m_utils.wrappingIntAddFunction(type);
|
||||
break;
|
||||
case Token::Sub:
|
||||
fun = checked ? m_utils.overflowCheckedIntSubFunction(type) : m_utils.wrappingIntSubFunction(type);
|
||||
break;
|
||||
case Token::Mul:
|
||||
fun = checked ? m_utils.overflowCheckedIntMulFunction(type) : m_utils.wrappingIntMulFunction(type);
|
||||
break;
|
||||
case Token::Div:
|
||||
fun = checked ? m_utils.overflowCheckedIntDivFunction(type) : m_utils.wrappingIntDivFunction(type);
|
||||
break;
|
||||
case Token::Mod:
|
||||
fun = m_utils.intModFunction(type);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(_type.category() == Type::Category::FixedPoint, "");
|
||||
FixedPointType const& type = dynamic_cast<FixedPointType const&>(_type);
|
||||
switch (_operator)
|
||||
{
|
||||
case Token::Add:
|
||||
fun = m_utils.fixedAddFunction(type, checked);
|
||||
break;
|
||||
case Token::Sub:
|
||||
fun = m_utils.fixedSubFunction(type, checked);
|
||||
break;
|
||||
case Token::Mul:
|
||||
fun = m_utils.fixedMulFunction(type, checked);
|
||||
break;
|
||||
case Token::Div:
|
||||
fun = m_utils.fixedDivFunction(type, checked);
|
||||
break;
|
||||
default:
|
||||
solAssert(false, "Unknown arithmetic operator.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,9 @@
|
||||
contract c {
|
||||
function mul(fixed a) public pure returns (fixed c) {
|
||||
return a * 1.5;
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// mul(fixed128x18): 100 -> 0x96
|
@ -0,0 +1,7 @@
|
||||
contract c {
|
||||
function mul(fixed a, fixed b) public pure returns (fixed c) {
|
||||
return a * b;
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// mul(fixed128x18,fixed128x18): 200, 10000000000000000000000 -> 2000000
|
@ -0,0 +1,11 @@
|
||||
contract C {
|
||||
fixed x = 1.7;
|
||||
fixed immutable y = -1.8;
|
||||
fixed public z = - 9.3;
|
||||
fixed constant w = -3.4;
|
||||
function f() public view returns (fixed, fixed, fixed, fixed) {
|
||||
return (x, y, this.z(), w);
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// f() ->
|
Loading…
Reference in New Issue
Block a user