f_minimal.go (3886B)
1 package main 2 3 import ( 4 "bufio" 5 "errors" 6 "fmt" 7 "git.nunosempere.com/NunoSempere/fermi/pretty" 8 "math" 9 "os" 10 "strconv" 11 "strings" 12 ) 13 14 type Lognormal struct { 15 low float64 16 high float64 17 } 18 19 const HELP_MSG = " Operation | Variable assignment | Special\n" + 20 " Operation: operator operand\n" + 21 " operator: (empty) | * | / | + | -\n" + 22 " operand: scalar | lognormal\n" + 23 " lognormal: low high\n" + 24 " Clear stack: . | c | clear\n" + 25 " Other special operations: help | debug | exit\n" + 26 " Examples: \n" + 27 " / 2.5\n" + 28 " * 1 10 (interpreted as lognormal)\n" + 29 " / 1 10\n" + 30 " 1 10 (multiplication taken as default operation)\n" + 31 " .\n" + 32 " exit\n" 33 const NORMAL90CONFIDENCE = 1.6448536269514727 34 const N_SAMPLES = 100_000 35 36 func prettyPrintLognormal(l Lognormal) { 37 fmt.Printf("=> ") 38 pretty.PrettyPrint2Floats(l.low, l.high) 39 fmt.Println() 40 } 41 42 func printAndReturnErr(err_msg string) error { 43 fmt.Println(err_msg) 44 fmt.Println(HELP_MSG) 45 return errors.New(err_msg) 46 } 47 48 func multiplyLogDists(l1 Lognormal, l2 Lognormal) Lognormal { 49 logmean1 := (math.Log(l1.high) + math.Log(l1.low)) / 2.0 50 logstd1 := (math.Log(l1.high) - math.Log(l1.low)) / (2.0 * NORMAL90CONFIDENCE) 51 52 logmean2 := (math.Log(l2.high) + math.Log(l2.low)) / 2.0 53 logstd2 := (math.Log(l2.high) - math.Log(l2.low)) / (2.0 * NORMAL90CONFIDENCE) 54 55 logmean_product := logmean1 + logmean2 56 logstd_product := math.Sqrt(logstd1*logstd1 + logstd2*logstd2) 57 58 h := logstd_product * NORMAL90CONFIDENCE 59 loglow := logmean_product - h 60 loghigh := logmean_product + h 61 return Lognormal{low: math.Exp(loglow), high: math.Exp(loghigh)} 62 } 63 64 func divideLogDists(l1 Lognormal, l2 Lognormal) (Lognormal, error) { 65 if l2.high == 0 || l2.low == 0 { 66 fmt.Println("Error: Can't divide by 0.0") 67 return Lognormal{}, errors.New("Error: division by zero") 68 } 69 return multiplyLogDists(l1, Lognormal{low: 1.0 / l2.high, high: 1.0 / l2.low}), nil 70 } 71 72 func parseWordsErr(err_msg string) (string, Lognormal, error) { 73 return "", Lognormal{}, printAndReturnErr(err_msg) 74 } 75 func parseWordsIntoOpAndDist(words []string) (string, Lognormal, error) { 76 op := "" 77 var dist Lognormal 78 79 switch words[0] { 80 case "*", "/": 81 op = words[0] 82 words = words[1:] 83 default: 84 op = "*" 85 } 86 87 switch len(words) { 88 case 0: 89 return parseWordsErr("Operator must have operand; can't operate on nothing") 90 case 1: 91 single_float, err := strconv.ParseFloat(words[0], 64) // abstract this away to search for K/M/B/T/etc. 92 if err != nil { 93 return parseWordsErr("Trying to operate on a scalar, but scalar is neither a float nor an assigned variable") 94 } 95 dist = Lognormal{low: single_float, high: single_float} 96 case 2: 97 new_low, err1 := strconv.ParseFloat(words[0], 64) 98 new_high, err2 := strconv.ParseFloat(words[1], 64) 99 if err1 != nil || err2 != nil { 100 return parseWordsErr("Trying to operate by a distribution, but distribution is not specified as two floats") 101 } 102 dist = Lognormal{low: new_low, high: new_high} 103 default: 104 return parseWordsErr("Input not understood or not implemented yet") 105 } 106 return op, dist, nil 107 } 108 109 func main() { 110 reader := bufio.NewReader(os.Stdin) 111 old_dist := Lognormal{low: 1, high: 1} 112 113 replForLoop: 114 for { 115 116 new_line, _ := reader.ReadString('\n') 117 words := strings.Split(strings.TrimSpace(new_line), " ") 118 119 if strings.TrimSpace(new_line) == "" { 120 continue replForLoop 121 } 122 123 op, new_dist, err := parseWordsIntoOpAndDist(words) 124 if err != nil { 125 continue replForLoop 126 } 127 switch op { 128 case "*": 129 old_dist = multiplyLogDists(old_dist, new_dist) 130 case "/": 131 result_dist, err := divideLogDists(old_dist, new_dist) 132 if err != nil { 133 continue replForLoop 134 } 135 old_dist = result_dist 136 137 } 138 prettyPrintLognormal(old_dist) 139 } 140 }