-
Notifications
You must be signed in to change notification settings - Fork 17
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
scrutinizer: add custom big Int implementation #249
Conversation
7a48e02
to
555183a
Compare
The JSON Marshaler for the standard type big.Int optputs an integer number instead of an string (such as "123456789") or an hexString (such as "0x123456). This behavior creates a problem when importing the JSON data to a different implementation (i.e Javascript) since it forces to read the field as an integer (instead of bigInt). To fix this we introduce a new custom big.Int implementation which Marshals always to a bigInt string. Note that this fix is widely used by projects such as go-ethereum (common/hexutil) Signed-off-by: p4u <[email protected]>
return (*big.Int)(i) | ||
} | ||
|
||
// Add sum x+y |
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.
I think adding this arithmetic function is a bit arbitrary, as math/big.Int
supports many more arithmetic operations. I would remove this method.
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.
I just added those which we are currently using. Its mainly Add and Mul. So to me its fine to have a simplified wrapper of the standard big.Int implementation
I have a question regarding the scope of this BigInt. Is the Or is it just a wrapper used at the edge to control the decoding and encoding of JSON? |
In general we follow the approcah "use the standard libraries always if possible". So the types.BigInt type would only be used on those packages that require its modified functionallity (those that handle structs that are marshaled to JSON). Currently just the scrutinizer. |
In that case, have you considered in r.Weight.ToInt().Add(r.Weight.ToInt(), weight)
// [...]
for q, value := range voteValues {
r.Votes[q][0].ToInt().Add(
r.Votes[q][0].ToInt(),
new(big.Int).Mul(
new(big.Int).SetUint64(uint64(value)),
weight),
)
} instead of r.Weight.Add(r.Weight, (*types.BigInt)(weight))
// [...]
for q, value := range voteValues {
r.Votes[q][0].Add(
r.Votes[q][0],
new(types.BigInt).Mul(
new(types.BigInt).SetUint64(uint64(value)),
(*types.BigInt)(weight)),
)
} This way there's no need to add new arithmetic methods to the wrapper. Also, maybe it's worth it renaming |
Well in this case results.go already uses the |
Lets also hear @mvdan voice regarding these matters, a third opinion is welcome :) |
I agree that we should marshal big ints as strings in JSON. I get that upstream says using numbers is OK, because JSON doesn't specify precision, so in theory it's fine. But in practice, most JSON libraries will use float64 or int64 for integers, meaning loss of precision. Doing this change in Go without writing our own wrapper should be easy, but unfortunately the current encoding/json library is just not powerful enough for it. I was hoping that the If golang/go#5901 were implemented today we could use that instead, but it doesn't exist yet. So, yup, a wrapper type is our only bet I think. That said, I'm not a fan of this particular wrapper, because it hides all methods from the underlying
Any places that need to do operations on these can do stuff like:
Similarly, you can convert a big.Int to our type like:
I get that that's slightly more verbose, but we also avoid mixing our type with our methods with big.Int and its methods. If our type is only about JSON marshaling and unmarshaling, then it's crystal clear that consuming or modifying the actual integer values uses big.Int, not our type. Plus we don't end up with two sets of methods that look the same and do the same, but actually are incompatible with each other as they take different types :) |
Here's a working example of what I mean: https://play.golang.org/p/62r5FjaQfpN |
I'm in favor of this approach, mainly because it focuses only on the encoding part but leaves the rest of the functionality of the |
I see your points, however IMHO its a too complex approach (from the developer point of view) in relation with the benefits of it. The wrapper approach works very similar to the standard big.Int implementation, thus it makes very easy for someone used to play with math/big. Its true that it hiddes methods but the scope of its usage is very limited and in the worst scenario you can do If exporting methods partially is what causes here the disruption, I'd instead still use the wrapper but remove Add() and Mul() and do instead At the end both solutions work and its more of a matter of personal taste than a pragmatic set of reasons for one or the other. |
Ummm, ok maybe I need to put the hands on the @mvdan approach to better understand the benefits of it. The snippet code is not compiling btw. |
So, maybe I'm not understanding something from the proposal, but with the struct approach any method used returns a big.Int type which makes it not convinient when writting code type BigInt struct {
*big.Int
}
a := new(BigInt).SetUint64(100)
b := new(BigInt).SetUint64(200)
var c BigInt
c.Add(c, b) // type casting error |
I think that in mvdan's proposal, the wrapper is not intended to be used as a pointer (it already wraps a pointer to x := BigInt{big.NewInt(123)}
x.Add(x.Int, big.NewInt(1)) Edit1: Ah no, I don't think that's related to the type casting error you encountered. I think the error comes from the fact that |
Yep. Or, as a more direct version of what Pau was trying to do:
It does require more verbosity with the And if you want to do lots of arithmetic, you can always obtain the
|
Ok, still I don't see a clear benefit. I'd go for the current approach (which just works) since we need to have this upstream ASAP (in order to finish the processArchive stuff) and I don't have time now for re-implementing it. I'll not oppose to a new PR changing to the @mvdan approach. If some of you want and have the time for doing it today, I'd wait. Else I'll merge and we can add a new issue for doing this in the future. |
Issue opened #250 |
The JSON Marshaler for the standard type big.Int optputs an integer
number instead of an string (such as 123456789 instead of "123456789").
This behavior creates a problem when importing the JSON
data to a different implementation (i.e Javascript) since it forces to
read the field as an integer.
To fix this we introduce a new custom big.Int implementation which
Marshals always to a bigInt string.
Note that this fix is widely used by projects such as go-ethereum
(common/hexutil)
Signed-off-by: p4u [email protected]