fermi

A minimalist calculator for estimating with distributions
Log | Files | Refs | README

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 }