diff --git a/commonxl/fmt.go b/commonxl/fmt.go index daecb1b..9416a72 100644 --- a/commonxl/fmt.go +++ b/commonxl/fmt.go @@ -145,6 +145,9 @@ func fracFmtFunc(n int) FmtFunc { return "MUST BE numeric TO FORMAT CORRECTLY" } w, n, d := DecimalToWholeFraction(f, n, n) + if n == 0 { + return fmt.Sprintf("%d", w) + } return fmt.Sprintf("%d %d/%d", w, n, d) } } diff --git a/commonxl/fmt_test.go b/commonxl/fmt_test.go index c5a9240..d844ff1 100644 --- a/commonxl/fmt_test.go +++ b/commonxl/fmt_test.go @@ -6,12 +6,12 @@ import ( "time" ) -type testcase struct { +type testcaseNums struct { v interface{} s string } -var commas = []testcase{ +var commas = []testcaseNums{ {10, "10"}, {float64(10), "10"}, {float64(10) + 0.12345, "10.12345"}, @@ -72,10 +72,11 @@ var commas = []testcase{ func TestCommas(t *testing.T) { cf := addCommas(identFunc) - for _, c := range commas { - if c.s != cf(nil, c.v) { - t.Fatalf("commas failed: did not get '%s' for %T(%v)", c.s, c.v, c.v) + fs := cf(nil, c.v) + if c.s != fs { + t.Fatalf("commas failed: get '%s' but expected '%s' for %T(%v)", + fs, c.s, c.v, c.v) } } } @@ -103,3 +104,35 @@ func TestDateFormats(t *testing.T) { } } } +func TestBoolFormats(t *testing.T) { + ff := makeFormatter(`"yes";"yes";"no"`) + + if "no" != ff(nil, false) { + t.Fatal(`false should be "no"`) + } + if "no" != ff(nil, 0) { + t.Fatal(`0 should be "no"`) + } + if "no" != ff(nil, 0.0) { + t.Fatal(`0.0 should be "no"`) + } + + ///// + + if "yes" != ff(nil, true) { + t.Fatal(`true should be "yes"`) + } + if "yes" != ff(nil, 99) { + t.Fatal(`99 should be "yes"`) + } + if "yes" != ff(nil, -4) { + t.Fatal(`-4 should be "yes"`) + } + + if "yes" != ff(nil, 4.0) { + t.Fatal(`4.0 should be "yes"`) + } + if "yes" != ff(nil, -99.0) { + t.Fatal(`-99.0 should be "yes"`) + } +} diff --git a/commonxl/frac_test.go b/commonxl/frac_test.go new file mode 100644 index 0000000..86bd419 --- /dev/null +++ b/commonxl/frac_test.go @@ -0,0 +1,63 @@ +package commonxl + +import ( + "math" + "testing" +) + +type testcaseFrac struct { + v float64 + s string + n int +} + +var fracs = []testcaseFrac{ + {10, "10", 1}, + {-10, "-10", 1}, + {10.5, "10 1/2", 1}, + {-10.5, "-10 1/2", 1}, + + {10.25, "10 1/4", 1}, + {10.75, "10 3/4", 1}, + {10.667, "10 2/3", 1}, + + {-10.25, "-10 1/4", 1}, + {-10.75, "-10 3/4", 1}, + {-10.667, "-10 2/3", 1}, + + {3.14159, "3 1/7", 1}, + {3.14159, "3 1/7", 2}, + {3.14159, "3 16/113", 3}, + {3.14159, "3 431/3044", 4}, + {3.14159, "3 3432/24239", 5}, + {3.14159, "3 14159/100000", 6}, + + {math.Pi, "3 1/7", 1}, + {math.Pi, "3 1/7", 2}, + {math.Pi, "3 16/113", 3}, // err = 2.6e-7 + {math.Pi, "3 16/113", 4}, // better because 431/3044 err = 2.6e-6 + {math.Pi, "3 14093/99532", 5}, + {math.Pi, "3 14093/99532", 6}, + + {-math.Pi, "-3 1/7", 1}, + {-math.Pi, "-3 1/7", 2}, + {-math.Pi, "-3 16/113", 3}, // err = 2.6e-7 + {-math.Pi, "-3 16/113", 4}, // better because 431/3044 err = 2.6e-6 + {-math.Pi, "-3 14093/99532", 5}, + {-math.Pi, "-3 14093/99532", 6}, + + // TODO: fixed denominator fractions (e.g. "??/8" ) + // TODO: string interpolations (e.g. '0 "pounds and " ??/100 "pence"') + // examples: https://bettersolutions.com/excel/formatting/number-tab-fractions.htm +} + +func TestFractions(t *testing.T) { + for _, c := range fracs { + ff := fracFmtFunc(c.n) + fs := ff(nil, c.v) + if c.s != fs { + t.Fatalf("fractions failed: got: '%s' expected: '%s' for %T(%v)", + fs, c.s, c.v, c.v) + } + } +} diff --git a/commonxl/numbers.go b/commonxl/numbers.go index 4ff8856..c3df4c2 100644 --- a/commonxl/numbers.go +++ b/commonxl/numbers.go @@ -8,8 +8,15 @@ import ( // number and fraction approximation with at most nn digits in the numerator // and nd digits in the denominator. func DecimalToWholeFraction(val float64, nn, nd int) (whole, num, den int) { - num, den = DecimalToFraction(val, nn, nd) - whole, num = num/den, num%den + wholeF, part := math.Modf(val) + if part == 0.0 { + return int(wholeF), 0, 1 + } + if part < 0.0 { + part = -part + } + whole = int(wholeF) + num, den = DecimalToFraction(part, nn, nd) return }