How do I parse a currency value as *big.Int in Go? - go

I want to parse a string like "12.49" into a *big.Int in Go. The resulting *big.Int should represent the amount of cents in the given value, in this case 1249. Here are some more examples of inputs and their expected outputs:
"3": 300
"3.1": 310
".19": 19
I already tried working with *big.Float and its Int function, but realized, that *big.Float does not provide arbitrary precision.
Right now I'm using this algorithm, but it seems fragile (Go Playground link):
func eurToCents(in string) *big.Int {
missingZerosUntilCents := 2
i := strings.Index(in, ".")
if i > -1 {
missingZerosUntilCents -= len(in) - i - 1
if missingZerosUntilCents < 0 {
panic("too many decimal places")
}
}
in = strings.Replace(in, ".", "", 1)
in += strings.Repeat("0", missingZerosUntilCents)
out, ok := big.NewInt(0).SetString(in, 10)
if !ok {
panic(fmt.Sprintf("could not parse '%s' as an interger", in))
}
return out
}
Is there a standard library function or other common way to parse currencies in Go? An external library is not an option.
PS: I'm parsing Nano cryptocurrency values, which have 30 decimal places and a maximum value of 133,248,297.0. That's why I'm asking for *big.Int and not uint64.

Update: Seems like this solution is still buggy, because an inaccurate result is reported after multiplication: https://play.golang.org/p/RS-DC6SeRwz
After revisiting the solution with *big.Float, I realized, that it does work perfectly fine. I think I forgot to use SetPrec on rawPerNano previously. I'm going to provide an example for the Nano cryptocurrency, because it requires many decimal places.
This works as expected (Go Playground link):
func nanoToRaw(in string) *big.Int {
f, _ := big.NewFloat(0).SetPrec(128).SetString(in)
rawPerNano, _ := big.NewFloat(0).SetPrec(128).SetString("1000000000000000000000000000000")
f.Mul(f, rawPerNano)
i, _ := f.Int(big.NewInt(0))
return i
}
Thanks #hymns-for-disco for nudging me in the right direction!

Related

Can 'map' and 'reduce' be implemented in Go with generics

I decided that now that generics have been introduced into Go that something like map/reduce should be possible. So, I took a naive stab at it and I get the error:
./prog.go:18:36: cannot use thing (variable of type int) as type I in argument to mapper
Which doesn't explain if the problem is fundamental or I am simply doing something wrong syntactically. Can generic map/reduce be implemented in Go?
package main
import "fmt"
func main() {
things := []int{1, 2, 3, 4}
results := Map(things, func(t int) int {
return t + 1
})
fmt.Printf("%v", results)
}
func Map[I interface{}, O interface{}](things []I, mapper func(thing I) O) []O {
results := make([]O, 0, len(things))
for thing := range things {
results = append(results, mapper(thing))
}
return results
}
You have incorrect use of range. A single variable extracted from range will be the index (type int), not the value (type I, which is only coincidentally int in this case).
Try
for _, thing := range things{...}
This can be done quite easily. You have an error in your code, though right here:
for thing := range things {
You are iterating over the index values (int), not the values of type I. You're also specifying 2 constraints (types I and O) both set to be interface{}. You can just use any instead (it's shorthand for interface{})
So simply write:
func Map[T any, O any](things []T, mapper func(thing T) O) []O {
result := make([]O, 0, len(things))
for _, thing := range things {
result = append(result, mapper(thing))
}
return result
}
Demo
This is quite closely related to some code I reviewed on codereview exchange here. After going through the code, and writing snippets with a ton of suggestions, I decided to just create a package and throw it up on github instead. You can find the repo here.
In it, there's some examples that may come in handy, or help you work through some other quirks WRT generics in golang. I wsa specifically thinking about this bit, where you can filter a generic map type using callbacks like so:
// given the sMap type
type sMap[K comparable, V any] struct {
mu *sync.RWMutex
m map[K]V
}
// Filter returns a map containing the elements that matched the filter callback argument
func (s *sMap[K, V]) Filter(cb func(K, V) bool) map[K]V {
s.mu.RLock()
defer s.mu.RUnlock()
ret := make(map[K]V, len(s.m))
for k, v := range s.m {
if cb(k, v) {
ret[k] = v
}
}
return ret
}

How to get the natural log of a big integer

I am trying to convert a string to integer and then to calculate its log.
My first approach was to convert the string using strconv library, but I got an error about the length of the string to be converted.
After that, I used math/big library which worked fine. Now I am not able to apply math.Log()on the resulted big integer.
Code:
package main
import (
"fmt"
"math"
"math/big"
)
func main() {
bb := "11948904162160164791281681976941230184120142151411311314211115130161285142991119211447"
bi := big.NewInt(0)
if _, ok := bi.SetString(bb, 10); ok {
fmt.Println(math.Log(bi))
} else {
fmt.Printf("error parsing line %#v\n", bb)
}
}
Error:
cannot use bi (type *big.Int) as type float64 in argument to math.Log
There are very few situations in which you'd need a precision greater than the one provided by the standard float64 type.
But just to satisfy any "midnight crazy ideas" (or even some very in-depth scientific research!) anyone might run into, Rob Pike's implementations of some operations with big floats are probably the best you can get right now with Go. The log function can be found here.

Convert string to int in GO

I am trying to convert a number into a slice of its digit in Go. My code is like this
stn := strconv.Itoa(2342)
starr := make([]int,0)
for i3,_ := range stn {
temp,_ := strconv.Atoi(stn[i3])
starr = append(starr,temp )
}
fmt.Println(starr)
The error is: "cannot use stn[i3] (type byte) as type string in argument to strconv.Atoi". I am used to Python so I try to follow the idea from Python but it does not seem to work. Reall appreciate any help
Answering the question behind the question, here's how you might convert an int into a slice of digits in go instead of using string conversions.
func digitSlice(input int) []int {
output := []int{}
for input > 0 {
output = append([]int{input % 10}, output...)
input = input / 10
}
return output
}
https://play.golang.org/p/tCLISl7Djah

Does converting a string to a float lose precision?

Problem
When converting a string to a float64, the fractional part of the float64 loses a significant amount of numbers.
Code
origVal := "0.00000628"
convVal, err := strconv.ParseFloat(origVal, 64)
if err == nil {
fmt.Printf("Original value: %s\nConverted value: %f\n", origVal, convVal)
}
Outputs:
Original value: 0.00000628
Converted value: 0.000006
The code is available on the Go Playground: https://play.golang.org/p/a8fH_JGug7l
Context
I am pulling data from an API. This API stringifies floating point numbers. I convert these stringified numbers to floats because I want to do some basic arithmetics on them.
I am fairly new to Go, so my apologies if the answer is straightforward.
The problem was not that the string was not correctly converted, but that Printf does, by default, not output the complete fractional part if it is long.
The following code prints the same as the original code but with 10 numbers after the decimal point:
origVal := "0.00000628"
convVal, err := strconv.ParseFloat(origVal, 64)
if err == nil && err2 ==nil {
fmt.Printf("Original value: %s\nConverted value: %.10f\n", origVal, convVal)
}
Thanks to #usr2564301 for the quick reply!

Looking for an elegant way to parse an Integer [duplicate]

This question already has answers here:
Convert string to integer type in Go?
(5 answers)
Closed 8 months ago.
Right now I am doing the following in order to parse an integer from a string and then convert it to int type:
tmpValue, _ := strconv.ParseInt(str, 10, 64) //returns int64
finalValue = int(tmpValue)
It is quite verbose, and definitely not pretty, since I haven't found a way to do the conversion in the ParseInt call. Is there a nicer way to do that?
It seems that the function strconv.Atoi does what you want, except that it works regardless of the bit size of int (your code seems to assume it's 64 bits wide).
If you have to write that once in your program than I see no problem. If you need at several places you can write a simple specialization wrapper, for example:
func parseInt(s string) (int, error) {
i, err := strconv.ParseInt(str, 10, 32) // or 64 on 64bit tip
return int(i), err
}
The standard library doesn't aim to supply (bloated) APIs for every possible numeric type and/or their combinations.
Don't forget to check for errors. For example,
package main
import (
"fmt"
"strconv"
)
func main() {
s := "123"
i, err := strconv.Atoi(s)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(i)
}
Output:
123

Resources