-
Notifications
You must be signed in to change notification settings - Fork 132
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
Implement SafeRatio
#121
Implement SafeRatio
#121
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me.
Idle question: cover 2
and uncover 2
seem incredibly common. Have you seen that in other work too, or is it particular to the need to manipulate these pseudo-128s?
Use this class if all inputs to the expression are uint64s, the output fits in a uint64, and all | ||
intermediate values fit in a uint128. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All the numerator factors together have to fit in u128? I suppose it would be better to do the math in different orders sometimes, multiply then divide, and so on, so the intermediates are small enough. If we're not doing that (and it's data dependent, so I don't think we can) then I'm not sure of the value of this compared to A*B/C. Did you run into the need to do this all at once?
If there's no overhead in the generated code in the simple A*B/C case, I suppose it's fine though. I'll try to see if that's the case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason to allow > 2 numerators is because I believe an interface that only allows A*B/C
is pretty restrictive. If we only exposed that interface and you needed to have 3 numerators, then you would have to do (A_1*A_2)*B/C
, which is strictly worse since A_1*A_2
has to fit in a uint64, instead of A_1*A_2*B
having to fit in a uint128.
Plus, due to integer rounding behavior during division, I'm not sure it would be possible to implement the optimization that you described.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could do A_1; A_2; mulw; C; divmodw; B mul. (taking some liberties with stack management in there). That would keep intermediates smaller. But I don't know how badly rounding would compound imprecision.
Anyway, since this does A*B/C efficiently, I don't see any reason to complain much about it doing more if you can ask it to do more. Though I guess I'd argue that the docs should be clearer about the order of the steps, so it's clearer what is meant by "intermediate values".
SafeRatio([Int(2), Int(100)], [Int(5)]), | ||
"""#pragma version 5 | ||
int 2 | ||
int 100 | ||
mulw | ||
int 0 | ||
int 5 | ||
divmodw | ||
pop | ||
pop | ||
swap | ||
! | ||
assert | ||
return | ||
""", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This convinces me about the "no overhead" on A*B/C
I've not seen a use for specifically
E.g. edit: just made an issue for it: algorand/go-algorand#2963 |
Implement
SafeRatio
, as described in #101