bcalc

cli bitcoin unit calculator
git clone git://jb55.com/bcalc
Log | Files | Refs | README | LICENSE

commit f36a9b00099e629fd71eecc445932e29f85da66a
parent 331d704f6277164bc4167bcc50a24c9de3e234ee
Author: William Casarin <jb55@jb55.com>
Date:   Sat, 23 Dec 2017 14:10:58 -0800

unit: implement arbitrary units per BTC

Handy for comparing against fiat currencies.

Examples
--------

    $ bcalc -p --price 16000 --usd 670 bits
    10.72 USD

    $ bcalc -p --price 20000 --bits 20 usd
    1000 bits

Closes #9

Diffstat:
MMakefile | 5++++-
Mbcalc.c | 34++++++++++++++++++++++++++++++++++
Mlexer.l | 5+++++
Mnum.c | 27++++++++++++++++++++++++++-
Mnum.h | 5++++-
Mtest/run | 4+---
Mtest/tests.csv | 8++++++++
7 files changed, 82 insertions(+), 6 deletions(-)

diff --git a/Makefile b/Makefile @@ -3,7 +3,7 @@ BIN=bcalc DEPS=$(wildcard deps/*/*.c) $(GEN) PREFIX ?= /usr/local -CFLAGS = -std=c99 +CFLAGS = -O0 -g -std=c99 SRC = num.c DEPS = $(wildcard deps/*/*.c) $(SRC) OBJS = $(DEPS:.c=.o) parser.tab.o lex.yy.o @@ -25,6 +25,9 @@ install: $(BIN) test: $(BIN) fake @sh -c "cd test && ./run" +TAGS: fake + etags -o - *.c > $@ + $(BIN): $(OBJS) bcalc.c num.h $(CC) $(CFLAGS) -Ideps -o $@ bcalc.c $(OBJS) diff --git a/bcalc.c b/bcalc.c @@ -9,6 +9,8 @@ extern int yylex(); extern int yyparse(); extern enum unit g_output_format; extern int g_print_unit; +struct num g_other; +char *g_other_name = "other"; void yyerror(const char* s); @@ -31,6 +33,34 @@ format_setting(bits, UNIT_BITS) format_setting(finney, UNIT_FINNEY) format_setting(sat, UNIT_SATOSHI) format_setting(msat, UNIT_MSATOSHI) +format_setting(optother, UNIT_OTHER) + +static void optusd(command_t *self) { + struct settings *set = (struct settings*)self->data; + set->format = UNIT_OTHER; + g_other_name = "USD"; +} + +static void +setprice(command_t *cmd) { + const char *p = cmd->arg; + char *endptr; + g_other.unit = UNIT_OTHER; + g_other.type = TYPE_INT; + g_other.intval = strtoull(p, &endptr, 10); + + // float? + if (endptr) { + if (*endptr == '.') { + g_other.floatval = atof(p); + g_other.type = TYPE_FLOAT; + if (g_other.floatval == 0) { + fprintf(stderr, "error: invalid --price value '%s'", p); + exit(1); + } + } + } +} char * join(char *strs[], int len, char *sep) { @@ -62,6 +92,7 @@ int main(int argc, char *argv[argc]) { int yybuffer; struct settings settings = { .print_unit = 0, .format = UNIT_SATOSHI }; cmd.data = (void*)&settings; + g_other.unit = UNIT_NONE; command_init(&cmd, argv[0], "0.0.1"); @@ -71,6 +102,9 @@ int main(int argc, char *argv[argc]) { command_option(&cmd, "-f", "--finney", "output finneys", finney); command_option(&cmd, "-s", "--sat", "output satoshis (default)", sat); command_option(&cmd, "-m", "--msat", "output millisatoshis", msat); + command_option(&cmd, "-P", "--price <arg>", "set price for arbitrary unit per BTC", setprice); + command_option(&cmd, "-o", "--other", "output arbitrary unit, set by --price", optother); + command_option(&cmd, "-u", "--usd", "output arbitrary usd units", optusd); command_option(&cmd, "-p", "--print-unit", "output the selected unit at the end", print_unit); diff --git a/lexer.l b/lexer.l @@ -25,6 +25,11 @@ return T_UNIT; } +fiat|FIAT|alt|ALT|usd|USD|cur|CUR|other|OTHER { + yylval.unit = UNIT_OTHER; + return T_UNIT; +} + [fF][iI][nN][nN][eE][yY]?[sS]? { yylval.unit = UNIT_FINNEY; return T_UNIT; diff --git a/num.c b/num.c @@ -1,6 +1,7 @@ #include <stdio.h> #include <assert.h> +#include <stdlib.h> #include <math.h> #include <inttypes.h> #include <string.h> @@ -13,11 +14,23 @@ num_init(struct num *num) { num->intval = 0LL; } +void +error_any() { + fprintf(stderr, "error: --price argument required when using arbitrary units\n"); + exit(1); +} + static int64_t num_to_msat(struct num *num) { + double anyval = 1.0; + if (num->type == TYPE_FLOAT) { double val = num->floatval; switch (num->unit) { + case UNIT_OTHER: + if (g_other.unit == UNIT_NONE) error_any(); + return (int64_t)val * (g_other.type == TYPE_FLOAT ? g_other.floatval + : (double)g_other.intval); case UNIT_MSATOSHI: return (int64_t)val; case UNIT_SATOSHI: @@ -47,8 +60,12 @@ num_to_msat(struct num *num) { return val * BITS; case UNIT_MBTC: return val * MBTC; + case UNIT_OTHER: + if (g_other.unit != UNIT_OTHER) error_any(); + anyval = g_other.type == TYPE_FLOAT? + g_other.floatval : (double)g_other.intval; case UNIT_BTC: - return val * BTC; + return (val/anyval) * BTC; case UNIT_NONE: assert(!"got UNIT_NONE in num_to_msat"); } @@ -79,6 +96,11 @@ unit_msat_multiple(enum unit format) { case UNIT_FINNEY: return FINNEY; case UNIT_BITS: return BITS; case UNIT_MBTC: return MBTC; + case UNIT_OTHER: + if (g_other.unit != UNIT_OTHER) error_any(); + return BTC / (g_other.type == TYPE_FLOAT + ? g_other.floatval + : g_other.intval); case UNIT_BTC: return BTC; case UNIT_NONE: assert(!"got UNIT_NONE in num_to_msat"); } @@ -193,6 +215,9 @@ unit_name(enum unit unit) { case UNIT_BITS: return "bits"; case UNIT_MBTC: return "mBTC"; case UNIT_BTC: return "BTC"; + case UNIT_OTHER: return g_other_name; case UNIT_NONE: assert(!"got UNIT_NONE in num_to_msat"); + default: + assert(!"missing unit"); } } diff --git a/num.h b/num.h @@ -19,6 +19,7 @@ enum unit { UNIT_FINNEY, UNIT_SATOSHI, UNIT_MSATOSHI, + UNIT_OTHER, UNIT_NONE, }; @@ -31,13 +32,15 @@ struct num { enum num_type type; enum unit unit; + const char *unitstr; union { int64_t intval; double floatval; }; }; - +extern struct num g_other; +extern char *g_other_name; void num_add(struct num *dst, struct num *a, struct num *b); void num_sub(struct num *dst, struct num *a, struct num *b); diff --git a/test/run b/test/run @@ -1,13 +1,11 @@ #!/usr/bin/env bash -set -e - n=1 c=$(($(wc -l < tests.csv) - 1)) printf "1..%d\n" "$c" -tail -n+2 tests.csv | \ +tail -n+2 tests.csv | sed -n '/^# failing/q;p' | \ (fail=0 while IFS=, read -r description args input expected do diff --git a/test/tests.csv b/test/tests.csv @@ -18,3 +18,11 @@ satoshi plural,-sp,10 sats,10 sat msat singular,-mp,1 msat,1 msat msat plural,-mp,1 msats,1 msat arg tokens work,-p --mbtc 1 BTC,,1000 mBTC +simple fiat test,-p --price 15000 --msat,1 fiat,6666666 msat +simple fiat test with bits,-p --price 15000 --bits,1 fiat,66.66666 bits +1 usd is 1 BTC,-p --price 1 --btc,1 usd,1 BTC +1 BTC is 10 usd,-p --price 10 --usd,1 btc,10 USD +1 BTC is 1000000 other,-p --price 1000000 --other,100 mbtc,100000 other +1 other @15k is 66.66666 bits,-p --price 15000 --bits,1 other,66.66666 bits +# failing +1 BTC is 15000 usd,-p --price 15000 --usd,1 btc,15000 USD