/* ***************************************************************************** * bcd/str.c -- make a string out of a libg1m BCD. * Copyright (C) 2017 Thomas "Cakeisalie5" Touhey * * This file is part of libg1m. * libg1m is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 3.0 of the License, * or (at your option) any later version. * * libg1m is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with libg1m; if not, see . * * These functions are experimental! * I wrote most of this while comparing the results to what I wanted. * If you think you can write a clean equivalent of this, while understanding * the theory behind what you do, please try! :) * ************************************************************************** */ #include /** * getsize: * Get the size. * * @arg exp the exponent. * @arg neg whether the number is negative or not. * @arg offset the offset. * @arg digits the number of digits. * @return the size. */ static size_t getsize(int exp, int neg, int offset, int digits) { size_t sz = digits; if (neg) sz++; if (offset + digits < 0) sz += -digits - offset; /* zeroes */ if (offset + digits > 0) sz++; /* point */ if (offset >= 0) sz += 1 /* leading zero */ + offset; if (exp) { sz++; /* e */ if (exp < 0) sz += 1; int eexp = abs(exp); if (eexp >= 100) sz += 3; else if (eexp >= 10) sz += 2; else sz += 1; } return (sz); } /** * g1m_bcdtoa: * Make a string out of a BCD number. * * If the buffer is not big enough, the number will not be written down. * Will terminate the buffer if the string is written. * * @arg bcd the BCD. * @arg buf the buffer. * @arg len the length. * @return the required length if needed, otherwise, zero. */ size_t g1m_bcdtoa(const g1m_bcd_t *bcd, char *buf, size_t len) { /* get base digit */ int exp = g1m_bcd_exponent(bcd), offset = 0; int leftdigits = g1m_bcd_precision(bcd); const char *digits = bcd->g1m_bcd_mant; for (; leftdigits > 0; leftdigits--) { if (*digits) break; exp--; digits++; } /* get number of digits, check if zero */ for (; leftdigits && !digits[leftdigits - 1]; leftdigits--); if (!leftdigits) { int l = (len >= 2) ? 0 : 2; memcpy(buf, "0", 2 - l); return (len); } /* try to play with the settings to make a more acceptable output */ int neg = !!g1m_bcd_is_negative(bcd); int acceptable_alt = max(0, 5 - leftdigits); if (-acceptable_alt < exp && exp <= 5) { offset -= exp; exp = 0; } if (offset >= 0) { exp -= offset + 1; offset = -1; } /* calculate the size */ size_t sz = getsize(exp, neg, offset, leftdigits); /* check if there's enough space in the buffer */ if (sz > len) return (sz); /* then fill */ int origdigits = leftdigits; if (neg) *buf++ = '-'; for (; leftdigits && offset < 0; offset++, leftdigits--) *buf++ = *digits++ + '0'; for (; offset < 0; offset++) *buf++ = '0'; if (leftdigits == origdigits) *buf++ = '0'; if (offset > 0 || leftdigits) *buf++ = '.'; for (; offset > 0; offset--) *buf++ = '0'; while (leftdigits--) *buf++ = *digits++ + '0'; if (exp) { *buf++ = 'e'; if (exp < 0) { *buf++ = '-'; exp = -exp; } if (exp >= 100) *buf++ = exp / 100 + '0'; if (exp >= 10) *buf++ = (exp % 100) / 10 + '0'; *buf++ = exp % 10 + '0'; } *buf = 0; /* it's done! */ return (0); }