Browse Source

%e and %g support in fmt.printf

gingerBill 6 years ago
parent
commit
332e598357
3 changed files with 126 additions and 26 deletions
  1. 17 8
      core/decimal/decimal.odin
  2. 34 3
      core/fmt/fmt.odin
  3. 75 15
      core/strconv/strconv.odin

+ 17 - 8
core/decimal/decimal.odin

@@ -130,10 +130,15 @@ shift_right :: proc(a: ^Decimal, k: uint) {
 }
 
 shift_left :: proc(a: ^Decimal, k: uint) {
-	delta := int(k/4);
+	// NOTE(bill): used to determine buffer size required for the decimal from the binary shift
+	// 'k' means `1<<k` == `2^k` which equates to roundup(k*log10(2)) digits required
+	log10_2 :: 0.301029995663981195213738894724493026768189881462108541310;
+	capacity := int(f64(k)*log10_2 + 1);
 
-	r := a.count;       // read index
-	w := a.count+delta; // write index
+	r := a.count;          // read index
+	w := a.count+capacity; // write index
+
+	d := len(a.digits);
 
 	n: uint;
 	for r -= 1; r >= 0; r -= 1 {
@@ -141,7 +146,7 @@ shift_left :: proc(a: ^Decimal, k: uint) {
 		quo := n/10;
 		rem := n - 10*quo;
 		w -= 1;
-		if w < len(a.digits) {
+		if w < d {
 			a.digits[w] = byte('0' + rem);
 		} else if rem != 0 {
 			a.trunc = true;
@@ -153,7 +158,7 @@ shift_left :: proc(a: ^Decimal, k: uint) {
 		quo := n/10;
 		rem := n - 10*quo;
 		w -= 1;
-		if 0 <= w && w < len(a.digits) {
+		if w < d {
 			a.digits[w] = byte('0' + rem);
 		} else if rem != 0 {
 			a.trunc = true;
@@ -161,9 +166,12 @@ shift_left :: proc(a: ^Decimal, k: uint) {
 		n = quo;
 	}
 
-	a.count += delta;
-	a.count = min(a.count, len(a.digits));
-	a.decimal_point += delta;
+	// NOTE(bill): Remove unused buffer size
+	assert(w >= 0);
+	capacity -= w;
+
+	a.count = min(a.count+capacity, d);
+	a.decimal_point += capacity;
 	trim(a);
 }
 
@@ -253,3 +261,4 @@ rounded_integer :: proc(a: ^Decimal) -> u64 {
 	}
 	return n;
 }
+

+ 34 - 3
core/fmt/fmt.odin

@@ -551,9 +551,6 @@ _pad :: proc(fi: ^Info, s: string) {
 
 fmt_float :: proc(fi: ^Info, v: f64, bit_size: int, verb: rune) {
 	switch verb {
-	// case 'e', 'E', 'f', 'F', 'g', 'G', 'v':
-	// case 'f', 'F', 'v':
-
 	case 'f', 'F', 'v':
 		prec: int = 3;
 		if fi.prec_set do prec = fi.prec;
@@ -588,6 +585,40 @@ fmt_float :: proc(fi: ^Info, v: f64, bit_size: int, verb: rune) {
 			_pad(fi, str[1:]);
 		}
 
+	case 'e', 'E':
+		prec: int = 3;
+		if fi.prec_set do prec = fi.prec;
+		buf: [386]byte;
+
+		str := strconv.append_float(buf[1:], v, 'e', prec, bit_size);
+		str = string(buf[:len(str)+1]);
+		if str[1] == '+' || str[1] == '-' {
+			str = str[1:];
+		} else {
+			str[0] = '+';
+		}
+
+		if fi.space && !fi.plus && str[0] == '+' {
+			str[0] = ' ';
+		}
+
+		if len(str) > 1 && (str[1] == 'N' || str[1] == 'I') {
+			strings.write_string(fi.buf, str);
+			return;
+		}
+
+		if fi.plus || str[0] != '+' {
+			if fi.zero && fi.width_set && fi.width > len(str) {
+				strings.write_byte(fi.buf, str[0]);
+				fmt_write_padding(fi, fi.width - len(str));
+				strings.write_string(fi.buf, str[1:]);
+			} else {
+				_pad(fi, str);
+			}
+		} else {
+			_pad(fi, str[1:]);
+		}
+
 	case:
 		fmt_bad_verb(fi, verb);
 	}

+ 75 - 15
core/strconv/strconv.odin

@@ -213,28 +213,28 @@ append_float :: proc(buf: []byte, f: f64, fmt: byte, prec, bit_size: int) -> str
 
 
 
-DecimalSlice :: struct {
+Decimal_Slice :: struct {
 	digits:        []byte,
 	count:         int,
 	decimal_point: int,
 	neg:           bool,
 }
 
-FloatInfo :: struct {
+Float_Info :: struct {
 	mantbits: uint,
 	expbits:  uint,
 	bias:     int,
 }
 
 
-_f16_info := FloatInfo{10, 5,   -15};
-_f32_info := FloatInfo{23, 8,  -127};
-_f64_info := FloatInfo{52, 11, -1023};
+_f16_info := Float_Info{10, 5,   -15};
+_f32_info := Float_Info{23, 8,  -127};
+_f64_info := Float_Info{52, 11, -1023};
 
 
 generic_ftoa :: proc(buf: []byte, val: f64, fmt: byte, prec, bit_size: int) -> []byte {
 	bits: u64;
-	flt: ^FloatInfo;
+	flt: ^Float_Info;
 	switch bit_size {
 	case 32:
 		bits = u64(transmute(u32)f32(val));
@@ -276,11 +276,11 @@ generic_ftoa :: proc(buf: []byte, val: f64, fmt: byte, prec, bit_size: int) -> [
 	d := &d_;
 	assign(d, mant);
 	shift(d, exp - int(flt.mantbits));
-	digs: DecimalSlice;
+	digs: Decimal_Slice;
 	shortest := prec < 0;
 	if shortest {
 		round_shortest(d, mant, exp, flt);
-		digs = DecimalSlice{digits = d.digits[:], count = d.count, decimal_point = d.decimal_point};
+		digs = Decimal_Slice{digits = d.digits[:], count = d.count, decimal_point = d.decimal_point};
 		switch fmt {
 		case 'e', 'E': prec = digs.count-1;
 		case 'f', 'F': prec = max(digs.count-digs.decimal_point, 0);
@@ -297,14 +297,14 @@ generic_ftoa :: proc(buf: []byte, val: f64, fmt: byte, prec, bit_size: int) -> [
 			round(d, prec);
 		}
 
-		digs = DecimalSlice{digits = d.digits[:], count = d.count, decimal_point = d.decimal_point};
+		digs = Decimal_Slice{digits = d.digits[:], count = d.count, decimal_point = d.decimal_point};
 	}
 	return format_digits(buf, shortest, neg, digs, prec, fmt);
 }
 
 
 
-format_digits :: proc(buf: []byte, shortest: bool, neg: bool, digs: DecimalSlice, prec: int, fmt: byte) -> []byte {
+format_digits :: proc(buf: []byte, shortest: bool, neg: bool, digs: Decimal_Slice, prec: int, fmt: byte) -> []byte {
 	Buffer :: struct {
 		b: []byte,
 		n: int,
@@ -347,12 +347,72 @@ format_digits :: proc(buf: []byte, shortest: bool, neg: bool, digs: DecimalSlice
 		return to_bytes(b);
 
 	case 'e', 'E':
-		panic("strconv: e/E float printing is not yet supported");
-		return to_bytes(b); // TODO
+		add_bytes(&b, neg ? '-' : '+');
+
+		ch := byte('0');
+		if digs.count != 0 {
+			ch = digs.digits[0];
+		}
+		add_bytes(&b, ch);
+
+		if prec > 0 {
+			add_bytes(&b, '.');
+			i := 1;
+			m := min(digs.count, prec+1);
+			if i < m {
+				add_bytes(&b, ..digs.digits[i:m]);
+				i = m;
+			}
+			for ; i <= prec; i += 1 {
+				add_bytes(&b, '0');
+			}
+		}
+
+		add_bytes(&b, fmt);
+		exp := digs.decimal_point-1;
+		if digs.count == 0 {
+			// Zero has exponent of 0
+			exp = 0;
+		}
+
+		ch = '+';
+		if exp < 0 {
+			ch = '-';
+			exp = -exp;
+		}
+		add_bytes(&b, ch);
+
+		switch {
+		case exp < 10:  add_bytes(&b, '0', byte(exp)+'0'); // add prefix 0
+		case exp < 100: add_bytes(&b, byte(exp/10)+'0',  byte(exp%10)+'0');
+		case:           add_bytes(&b, byte(exp/100)+'0', byte(exp/10)%10+'0', byte(exp%10)+'0');
+		}
+
+		return to_bytes(b);
 
 	case 'g', 'G':
-		panic("strconv: g/G float printing is not yet supported");
-		return to_bytes(b); // TODO
+		eprec := prec;
+		if eprec > digs.count && digs.count >= digs.decimal_point {
+			eprec = digs.count;
+		}
+
+		if shortest {
+			eprec = 6;
+		}
+
+		exp := digs.decimal_point - 1;
+		if exp < -4 || exp >= eprec {
+			if prec > digs.count {
+				prec = digs.count;
+			}
+			return format_digits(buf, shortest, neg, digs, prec-1, fmt+'e'-'g'); // keep the same case
+		}
+
+		if prec > digs.decimal_point {
+			prec = digs.count;
+		}
+
+		return format_digits(buf, shortest, neg, digs, max(prec-digs.decimal_point, 0), 'f');
 
 	case:
 		add_bytes(&b, '%', fmt);
@@ -362,7 +422,7 @@ format_digits :: proc(buf: []byte, shortest: bool, neg: bool, digs: DecimalSlice
 
 }
 
-round_shortest :: proc(d: ^Decimal, mant: u64, exp: int, flt: ^FloatInfo) {
+round_shortest :: proc(d: ^Decimal, mant: u64, exp: int, flt: ^Float_Info) {
 	if mant == 0 { // If mantissa is zero, the number is zero
 		d.count = 0;
 		return;