Note: Your code is not useful. Rational numbers are available in the Go math/big
standard library. For example,
package main
import (
"fmt"
"math/big"
)
func main() {
fmt.Println("Enter a number:")
var r = new(big.Rat)
fmt.Scanln(r)
fmt.Println(r)
}
I read your code, the code you imported from github.com/retep-mathwizard
, and the code from rolfl's answer. I see problems.
Let's start with something simple, the calculation of the greatest common divisor or factor.
You embedded this code in a slab of code. It should be a function. For example,
func gcf(numer, denom int) int {
var gcf int
for i := numer/2 + 1; i >= 0; i-- {
if (numer%i == 0) && (denom%i == 0) {
gcf = i
break
}
}
return gcf
}
It's very inefficient. For well over two thousand years we have known of a much better way to do this.
Rolfl used a much better algorithm.
// Euclidian algorithm
func gcf(a, b int64) int64 {
if a < b {
return gcf(b, a)
}
if b == 0 {
return a
}
a = a % b
return gcf(b, a)
}
If you put blank lines between each sentence you can't see very much on a single screen. It's essential that code be readable. Think of blank lines as more like paragraph separators.
The algorithm is implemented using recursion. Any recursive algorithm can be written as an iterative algorithm. Go doesn't have tail recursion. Limit the use of recursion to the few cases where recursion is much easier to understand than iteration.
Here's an idiomatic Go GCD function. It's simple, direct, and fast.
func gcd(x, y int64) int64 {
for y != 0 {
x, y = y, x%y
}
return x
}
Here are some Go benchmarks which show how slow your GCF algorithm is.
BenchmarkGCFXXX 30000 54564 ns/op
BenchmarkGCFPeter 10000000 156 ns/op
Go types play an essential role in writing idiomatic Go. Rational
is an obvious type:
// A rational number r is expressed as the fraction p/q of two integers:
// r = p/q = (d*i+n)/d.
type Rational struct {
i int64 // integer
n int64 // fraction numerator
d int64 // fraction denominator
}
Like many types, Rational
should have a formatted string function. Your formatted output is ugly
7182818284590 and 141592653
-------
1000000000
I think this looks much better
7182818284590 + 141592653/1000000000
Here's a Go String
method for the Rational
type. Note the attention to pretty-printing details.
func (r Rational) String() string {
var s string
if r.i != 0 {
s += strconv.FormatInt(r.i, 10)
}
if r.n != 0 {
if r.i != 0 {
s += " + "
}
if r.d < 0 {
r.n *= -1
r.d *= -1
}
s += strconv.FormatInt(r.n, 10) + "/" + strconv.FormatInt(r.d, 10)
}
if len(s) == 0 {
s += "0"
}
return s
}
An obvious indication of problems is the difficulty in testing. Your program only handles one input value. Rolfl hardcoded a single fixed value: num := "12345.6785"
.
For a problem like this, which is driven by user input, the Go main function should be an input loop. For example,
func main() {
snr := bufio.NewScanner(os.Stdin)
enter := "Enter a decimal number:"
for fmt.Println(enter); snr.Scan(); fmt.Println(enter) {
d := snr.Text()
if len(d) == 0 {
break
}
r, err := ParseDecimal(d)
if err != nil {
fmt.Fprintln(os.Stderr, "Input error:", err)
continue
}
fmt.Println(r)
}
if err := snr.Err(); err != nil {
if err != io.EOF {
fmt.Fprintln(os.Stderr, err)
}
}
}
Now we have an easy way to test. Let's make sure that we can handle all reasonable values. Here are some values that you don't handle.
0; 0.; -0; 1; -1; -1.1; 1.;
and so on.
You should always expect the worst from user input and handle errors gracefully. For example,'
func ParseDecimal(s string) (r Rational, err error) {
sign := int64(1)
if strings.HasPrefix(s, "-") {
sign = -1
}
p := strings.IndexByte(s, '.')
if p < 0 {
p = len(s)
}
if i := s[:p]; len(i) > 0 {
if i != "+" && i != "-" {
r.i, err = strconv.ParseInt(i, 10, 64)
if err != nil {
return Rational{}, err
}
}
}
if p >= len(s) {
p = len(s) - 1
}
if f := s[p+1:]; len(f) > 0 {
n, err := strconv.ParseUint(f, 10, 64)
if err != nil {
return Rational{}, err
}
d := math.Pow10(len(f))
if math.Log2(d) > 63 {
err = fmt.Errorf(
"ParseDecimal: parsing %q: value out of range", f,
)
return Rational{}, err
}
r.n = int64(n)
r.d = int64(d)
if g := gcd(r.n, r.d); g != 0 {
r.n /= g
r.d /= g
}
r.n *= sign
}
return r, nil
}
Even if you write impeccable code, you will still be judged on the quality of the external packages that you use. The packages from github.com/retep-mathwizard/
are of very low quality.
Here's my complete program. It's a first draft.
package main
import (
"bufio"
"fmt"
"io"
"math"
"os"
"strconv"
"strings"
)
// A rational number r is expressed as the fraction p/q of two integers:
// r = p/q = (d*i+n)/d.
type Rational struct {
i int64 // integer
n int64 // fraction numerator
d int64 // fraction denominator
}
func (r Rational) String() string {
var s string
if r.i != 0 {
s += strconv.FormatInt(r.i, 10)
}
if r.n != 0 {
if r.i != 0 {
s += " + "
}
if r.d < 0 {
r.n *= -1
r.d *= -1
}
s += strconv.FormatInt(r.n, 10) + "/" + strconv.FormatInt(r.d, 10)
}
if len(s) == 0 {
s += "0"
}
return s
}
func gcd(x, y int64) int64 {
for y != 0 {
x, y = y, x%y
}
return x
}
func ParseDecimal(s string) (r Rational, err error) {
sign := int64(1)
if strings.HasPrefix(s, "-") {
sign = -1
}
p := strings.IndexByte(s, '.')
if p < 0 {
p = len(s)
}
if i := s[:p]; len(i) > 0 {
if i != "+" && i != "-" {
r.i, err = strconv.ParseInt(i, 10, 64)
if err != nil {
return Rational{}, err
}
}
}
if p >= len(s) {
p = len(s) - 1
}
if f := s[p+1:]; len(f) > 0 {
n, err := strconv.ParseUint(f, 10, 64)
if err != nil {
return Rational{}, err
}
d := math.Pow10(len(f))
if math.Log2(d) > 63 {
err = fmt.Errorf(
"ParseDecimal: parsing %q: value out of range", f,
)
return Rational{}, err
}
r.n = int64(n)
r.d = int64(d)
if g := gcd(r.n, r.d); g != 0 {
r.n /= g
r.d /= g
}
r.n *= sign
}
return r, nil
}
func main() {
snr := bufio.NewScanner(os.Stdin)
enter := "Enter a decimal number:"
for fmt.Println(enter); snr.Scan(); fmt.Println(enter) {
d := snr.Text()
if len(d) == 0 {
break
}
r, err := ParseDecimal(d)
if err != nil {
fmt.Fprintln(os.Stderr, "Input error:", err)
continue
}
fmt.Println(r)
}
if err := snr.Err(); err != nil {
if err != io.EOF {
fmt.Fprintln(os.Stderr, err)
}
}
}