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

Implement SafeRatio #121

Merged
merged 3 commits into from
Sep 25, 2021
Merged

Implement SafeRatio #121

merged 3 commits into from
Sep 25, 2021

Conversation

jasonpaulos
Copy link
Contributor

Implement SafeRatio, as described in #101

Copy link

@jannotti jannotti left a 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?

Comment on lines +80 to +81
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.

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.

Copy link
Contributor Author

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.

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

Comment on lines +1345 to +1359
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
""",

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

@jannotti jannotti merged commit 3ca49d3 into master Sep 25, 2021
@jannotti jannotti deleted the saferatio branch September 25, 2021 15:11
@jasonpaulos
Copy link
Contributor Author

jasonpaulos commented Sep 27, 2021

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?

I've not seen a use for specifically (un)cover 2 beyond this application. Though, for spilling slots to the stack before calling a recursive subroutine (see #114 for examples/more info), there is certainly a need for a cycle opcode that @vluchangco originally described:

cycle n m : rotates the top n elements of the stack m places (i.e., so top of the stack now contains the element that was m elements down, and the element that was at the top of the stack has n-m elements above it). Fails if there are fewer than n elements on the stack or if m > n. cycle n 0 and cycle n n are both no-ops unless there are fewer than n elements on the stack.

E.g. cycle 5 2 would replace uncover 4; uncover 4; uncover 4

edit: just made an issue for it: algorand/go-algorand#2963

@jasonpaulos jasonpaulos mentioned this pull request Sep 27, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants