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

Fixed point types #409

Open
4 of 7 tasks
chriseth opened this issue Mar 3, 2016 · 78 comments
Open
4 of 7 tasks

Fixed point types #409

chriseth opened this issue Mar 3, 2016 · 78 comments
Labels
epic effort Multi-stage task that may require coordination between team members across multiple PRs. high impact Changes are very prominent and affect users or the project in a major way. language design :rage4: Any changes to the language, e.g. new features selected for development It's on our short-term development

Comments

@chriseth
Copy link
Contributor

chriseth commented Mar 3, 2016

https://www.pivotaltracker.com/story/show/81779716

TODO:

  • assignment (lvalue and rvalue)
  • conversion (between different fixed types)
  • conversion (between other types)
  • comparison operators (< > =)
  • unary operators (-, --, ++)
  • binary operators (+ - / * )
  • do-we-need-these? binary operators (% **)
@chriseth chriseth added this to the bieti milestone Mar 3, 2016
@chriseth chriseth modified the milestones: colocolo, bieti Mar 29, 2016
@chriseth chriseth modified the milestones: domesticus, colocolo Apr 11, 2016
@chriseth chriseth changed the title Fixed point types Fixed point types - ConstantNumber part Apr 11, 2016
@chriseth chriseth changed the title Fixed point types - ConstantNumber part Fixed point types Apr 11, 2016
@chriseth chriseth added active and removed planned labels Apr 15, 2016
@chriseth chriseth added planned and removed active labels Apr 25, 2016
@chriseth
Copy link
Contributor Author

Some notes:

IntegerType::binaryOperatorResult is too tight, this should work but seems not to: uint128(1) + ufixed(2), same with FixedPointType::binaryOperatorResult

also this should work: .5 * uint128(7)

what happens currently with uint(7) / 2? Is it identical to .5 * uint(7)?

add test about signed mod with rational constants (should behave identical to SMOD opcode)

@VoR0220
Copy link
Member

VoR0220 commented May 12, 2016

signed mod as in a modulus operation with signed rational constants?

@chriseth
Copy link
Contributor Author

chriseth commented May 12, 2016

yes - and fixed point

@VoR0220
Copy link
Member

VoR0220 commented May 12, 2016

alright. I'll get on that. Crafting tests. Working on some fixes. And then it's onto the actual compilation.

@VoR0220 VoR0220 self-assigned this May 13, 2016
@VoR0220
Copy link
Member

VoR0220 commented May 13, 2016

so couple of things. I got your first one working.

ufixed a = uint128(1) + ufixed(2);

The second two in the examples you laid out, based on how we defined implicit conversion are currently impossible.

0.5 currently converts to ufixed0x8 and that does not convert with a uint128.

uint(7) / 2 stays a uint256 after the division by 2 (it's truncating), and therefore cannot convert to ufixed128x128. And no... 0.5 * uint(7) is not the same as uint(7)/2....one is a ufixed0x8 and the other is dividing by an integer....kind of confusing....I'm thinking we may need to fix this up. Open to all suggestions....the only thing I can think up right now is to create a class atop integer type and fixed point and make it a super class of some kind...

@chriseth chriseth modified the milestones: 2-functionality, domesticus Aug 5, 2016
@VoR0220 VoR0220 mentioned this issue Sep 7, 2016
9 tasks
@chriseth
Copy link
Contributor Author

chriseth commented Dec 2, 2016

We decided to rather denote decimal places instead of "number of bits after the comma" to reduce confusion among users. This means that
fixed64x7 is a type of 64 bits and a value x of this type is interpreted as the number x / 10**7.

The type fixed is an alias for fixed128x19. The reason is that this type simplifies multiplication and also allows conversion from int64 without loss of precision / range.

@VoR0220
Copy link
Member

VoR0220 commented Dec 2, 2016

@chriseth do we still want to allow users to fully extend the decimal range so that it can be fixed0x32 (I believe that's the full amount that it could take in but may be wrong).

@chriseth
Copy link
Contributor Author

chriseth commented Dec 2, 2016

@VoR0220 note that the 0 is the total amount of bits in the type. The drawback of using decimals is that you cannot force the value to be between 0 and 1 anymore (because that does not fit the decimal range).

@VoR0220
Copy link
Member

VoR0220 commented Dec 2, 2016

@chriseth so in other words it HAS to have an integer portion now...That's fine by me. The range and precisions seriously diminishes after 128 bits in fixed point either way.

@asselstine
Copy link

Sure.

First we let:

totalSupply = total # of tickets
balance = users # of tickets
previousPrize = the size of the last prize that was awarded
remainingTime = remaining number of seconds until prize
prizePeriodSeconds = total number of seconds between prizes

Then we calculate the exit fee:

exitFee = (remainingTime / prizePeriodSeconds) * (balance / totalSupply) * previousPrize

@3sGgpQ8H
Copy link

3sGgpQ8H commented May 8, 2020

I believe the result was that two fixed points could only be multiplied/divided if both of their types were 128 bits wide or less.

For me, forbidding multiplication of wide types makes the whole idea quite useless. I would prefer compiler to automatically apply different strategies for different types and even different values.

For example, when combined bit-widths of both arguments do not exceed 2^256-1, naive approach could be used:

return x * y / scale;

Otherwise, if scale is below 2^128-1, slightly more complicated approach is needed:

if (y == 0) return 0;
else {
    require (x / scale < uint(-1) / y); // Overflow protection, should be improved

    uint xh = x / scale;
    uint xl = x % scale;
    uint yh = y / scale;
    uint yl = y % scale;

    return xh * xh * scale + xh * yl + xl * yh + xl * yl / scale;
}

Probably, scale above 2^128-1 should be just forbidden, but if not, then for higher scales, compiler should use 512-bit intermediate arithmetic, like here: https://medium.com/coinmonks/math-in-solidity-part-3-percents-and-proportions-4db014e080b1#fe5c

@asselstine
Copy link

compiler should use 512-bit intermediate arithmetic

I love this idea. It would give us a lot more headroom over scaling the values.

@fulldecent
Copy link
Contributor

512-bit is not something we need to have in the first iteration of this.

@3sGgpQ8H
Copy link

Yes, but overflow semantic should not change after the initial release, otherwise it will be a mess.
So, one option would be to limit the maximum number of decimals to be 38, so scale factor will always fit into 128 bits. This way, multiplication could avoid using 512-bit internal logic. However, division will still be a problem. In my opinion, x / 2.0 should never overflow, but naive implementation like x * 1e18 / 2e18 would overflow on large x values. So for division, we probably need some king of wide arithmetic even in initial release.

Another option (the one I would prefer) is to include only literals, assignments, comparisons, ABI encode/decode, and reinterpret casts into the initial release of the feature in compiler, and leave arithmetic and conversion operations to be implemented in libraries, as there is no single “right” implementation for them.

@axic
Copy link
Member

axic commented Feb 10, 2021

Revisited the nice summary from the OZ forums this Monday, and discussed some of this on Gitter.

My current impression is that we should split this up into two phases (this was mentioned in the OZ forums too):

  1. Add a bare minimum support to the language and compile:
  • a single fixed128 type,
  • ABI coder,
  • conversion to same-width bytes,
  • perhaps, but not necessarily, conversion to same-width integer type
  1. Experiment with various implementations for different operators in the "stdlib" (Standard library #10282)

Even if we decide to move some operators into the compiler later on, I would argue that features like sqrt/pow/etc should stay in the stdlib.

@chriseth
Copy link
Contributor Author

Sounds like a good first milestone, so I would say let's do this! One important point: Conversion from non-integer literals should also be part of the first milestone.

@chriseth
Copy link
Contributor Author

Test ideas: check that e.g (1/3) == (1/3 + 1/3000000000000000) does not evaluate to true.

@gorgos
Copy link
Contributor

gorgos commented Jan 22, 2022

@chriseth What's the current status considering it's back in the icebox?

@hrkrshnn
Copy link
Member

hrkrshnn commented Jan 22, 2022

@gorgos we are not convinced about implementing these types and the corresponding operations since there is no canonical implementation. Existing implementations are vastly different. Binary v/s decimals. 512 bit multiplication v/s reverting on shadow overflow. Differences in rounding etc.

Currently, we want users to be able to implement this convinently using user defined value types and custom operators for it.

@3sGgpQ8H
Copy link

3sGgpQ8H commented Jan 22, 2022

@hrkrshnn It seems there is a chicken and egg problem: OpenZeppelin/openzeppelin-contracts#2265 (comment)

My suggestion about how this problem could be solved is that at stage one Solidity could implement a fixed-point type natively (probably several such types, it there is no definite winner among candidates) but don't implement arithmetic operations on such type, leaving operations for libraries. So, what Solidity should do at stage one: choose type name and allow declaring variables/arguments etc of such type, choose type semantics, binary representation and ABI encoding, add support for fractional literals translated to the new type, implement operators == and != for the new type, implement reinterpreting casts to/from the new type for existing types of the same bit width. What could be postponed to later stages: other operators such as +, -, *, /, **, <. >. <=, >=. Advanced functions such as exp and log.
Why the stage one features are important is because it will set the standard type to be supported by libraries, and will make this standard type convenient due to separate type name and human-friendly literals.

@frangio
Copy link
Contributor

frangio commented Jan 24, 2022

With the introduction of user defined value types I no longer think it's necessary for Solidity to implement a fixed point type.

Standardization of this type could (should?) happen as an EIP.

@ekpyron
Copy link
Member

ekpyron commented Jan 25, 2022

With the introduction of user defined value types I no longer think it's necessary for Solidity to implement a fixed point type.

Standardization of this type could (should?) happen as an EIP.

That's pretty much the status of this, yes - we're working towards allowing customly defined operators on user-defined types which will allow for a full implementation of fixed point types on the user side which can then, for example, be standardized as an EIP or we may also suggest a standard implementation in a compiler-integrated standard library.

@3sGgpQ8H
Copy link

3sGgpQ8H commented Jan 25, 2022

Custom type + custom operations on that type wouldn't solve the problem, as it is not possible to introduce convenient literals this way, however without literals fixed point code will remain unreadable. I believe, that nowadays decimal fixed-point formats prevail over binary fixed-point and floating-point formats solely because Solidity kind of support decimals fixed-point literals in the form 3.14e18, but doesn't support binary fixed-point or floating-point literals.
In my understanding, literals is the most important thing to make fractional operations convenient and readable. Core type is essential only because it is hard to imagine convenient literals for a custom type. Operations are least important, and I believe == and != would be enough at stage one.

@ekpyron
Copy link
Member

ekpyron commented Jan 25, 2022

Literals will be part of the complete user-defined type construct.

@cameel cameel added selected for development It's on our short-term development epic effort Multi-stage task that may require coordination between team members across multiple PRs. high impact Changes are very prominent and affect users or the project in a major way. labels Sep 14, 2022
@NunoFilipeSantos NunoFilipeSantos removed this from the 2-functionality milestone Apr 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
epic effort Multi-stage task that may require coordination between team members across multiple PRs. high impact Changes are very prominent and affect users or the project in a major way. language design :rage4: Any changes to the language, e.g. new features selected for development It's on our short-term development
Projects
None yet
Development

Successfully merging a pull request may close this issue.