1782 lines
42 KiB
C
1782 lines
42 KiB
C
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program 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 General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
/*
|
|
* decn.c
|
|
*
|
|
* Created on: Mar 21, 2019
|
|
* Author: jeffrey
|
|
*/
|
|
|
|
#include "../utils.h"
|
|
#include "../stack_debug.h"
|
|
|
|
#include "decn.h"
|
|
|
|
|
|
#define EXTRA_CHECKS
|
|
// #define DEBUG
|
|
// #define DEBUG_COMPARE_MAGN
|
|
// #define DEBUG_ADD
|
|
// #define DEBUG_MULT
|
|
// #define DEBUG_MULT_ALL //even more verbose
|
|
// #define DEBUG_DIV
|
|
// #define DEBUG_LOG
|
|
// #define DEBUG_LOG_ALL //even more verbose
|
|
// #define DEBUG_EXP
|
|
// #define DEBUG_EXP_ALL //even more verbose
|
|
// #define DEBUG_SQRT
|
|
|
|
#ifndef DESKTOP
|
|
//#undef EXTRA_CHECKS
|
|
#undef DEBUG
|
|
#undef DEBUG_COMPARE_MAGN
|
|
#undef DEBUG_ADD
|
|
#undef DEBUG_MULT
|
|
#undef DEBUG_DIV
|
|
#undef DEBUG_LOG
|
|
#undef DEBUG_LOG_ALL
|
|
#undef DEBUG_EXP
|
|
#undef DEBUG_EXP_ALL
|
|
#undef DEBUG_SQRT
|
|
#endif
|
|
|
|
#ifdef DESKTOP
|
|
#include <stdio.h>
|
|
#endif
|
|
|
|
#ifdef DESKTOP
|
|
#include <assert.h>
|
|
#else
|
|
#define assert(x)
|
|
#endif
|
|
|
|
#ifdef DESKTOP
|
|
static const uint8_t num_digits_display = DEC80_NUM_LSU*2;
|
|
#else
|
|
static const uint8_t num_digits_display = 16;
|
|
#endif
|
|
|
|
dec80 AccDecn;
|
|
__idata dec80 BDecn;
|
|
__idata dec80 TmpDecn; //used by add_decn() and mult_decn() and sqrt_decn()
|
|
__idata dec80 Tmp2Decn; //used by recip_decn(), ln_decn(), exp_decn(), sqrt_decn(), and sincos_decn()
|
|
__idata dec80 Tmp3Decn; //used by ln_decn(), exp_decn(), sqrt_decn(), and sincos_decn()
|
|
__xdata dec80 Tmp4Decn; //used by sincos_decn()
|
|
|
|
__xdata dec80 TmpStackDecn[4];
|
|
#define TMP_STACK_SIZE (sizeof TmpStackDecn / sizeof TmpStackDecn[0])
|
|
__idata uint8_t TmpStackPtr;
|
|
|
|
__xdata char Buf[DECN_BUF_SIZE];
|
|
|
|
//1 constant
|
|
const dec80 DECN_1 = {
|
|
0, {10, 0}
|
|
};
|
|
|
|
//ln(10) constant
|
|
const dec80 DECN_LN_10 = {
|
|
0, {23, 2, 58, 50, 92, 99, 40, 45, 68}
|
|
};
|
|
|
|
// 2 pi
|
|
const dec80 DECN_2PI = {
|
|
0, {62, 83, 18, 53, 7, 17, 95, 86, 48}
|
|
};
|
|
|
|
// pi/2
|
|
const dec80 DECN_PI2 = {
|
|
0, {15, 70, 79, 63, 26, 79, 48, 96, 62}
|
|
};
|
|
|
|
// 180/pi = 1rad in degree
|
|
const dec80 DECN_1RAD = {
|
|
1, {57, 29, 57, 79, 51, 30, 82, 32, 9}
|
|
};
|
|
|
|
void st_push_decn(const dec80 * const src)
|
|
{
|
|
copy_decn(&TmpStackDecn[TmpStackPtr], src);
|
|
TmpStackPtr++;
|
|
assert(TmpStackPtr < TMP_STACK_SIZE);
|
|
}
|
|
|
|
void st_pop_decn(dec80 * const dst)
|
|
{
|
|
assert(TmpStackPtr >= 1);
|
|
TmpStackPtr--;
|
|
if (dst) copy_decn(dst, &TmpStackDecn[TmpStackPtr]);
|
|
}
|
|
|
|
void st_load_decn(dec80 * const dst)
|
|
{
|
|
assert(TmpStackPtr >= 1);
|
|
copy_decn(dst, &TmpStackDecn[TmpStackPtr - 1]);
|
|
}
|
|
|
|
void copy_decn(dec80* const dest, const dec80* const src){
|
|
uint8_t i;
|
|
|
|
stack_debug(0x01);
|
|
dest->exponent = src->exponent;
|
|
|
|
//copy nibbles
|
|
for (i = 0; i < DEC80_NUM_LSU; i++){
|
|
dest->lsu[i] = src->lsu[i];
|
|
}
|
|
}
|
|
|
|
exp_t get_exponent(const dec80* const x){
|
|
exp_t exponent = x->exponent;
|
|
#ifdef EXP16
|
|
if (exponent & 0x4000){ //negative
|
|
return exponent | 0x8000;
|
|
} else { //positive
|
|
return exponent & 0x7fff;
|
|
}
|
|
#else
|
|
if (exponent & 0x40){ //negative
|
|
return exponent | 0x80;
|
|
} else { //positive
|
|
return exponent & 0x7f;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void set_exponent(dec80* acc, exp_t exponent, uint8_t num_is_neg){
|
|
#ifdef EXP16
|
|
if (num_is_neg){
|
|
exponent |= 0x8000;
|
|
} else {
|
|
exponent &= 0x7fff;
|
|
}
|
|
#else
|
|
if (num_is_neg){
|
|
exponent |= 0x80;
|
|
} else {
|
|
exponent &= 0x7f;
|
|
}
|
|
#endif
|
|
|
|
acc->exponent = exponent;
|
|
}
|
|
|
|
static void zero_remaining_dec80(dec80* dest, uint8_t digit100){
|
|
for ( ; digit100 < DEC80_NUM_LSU; digit100++){
|
|
dest->lsu[digit100] = 0;
|
|
}
|
|
}
|
|
|
|
|
|
static uint8_t shift_high = 0, shift_low = 0, shift_old = 0;
|
|
static uint8_t shift_i;
|
|
static void shift_right(dec80* x){
|
|
shift_high = shift_low = shift_old = 0;
|
|
for (shift_i = 0; shift_i < DEC80_NUM_LSU; shift_i++){
|
|
shift_high = x->lsu[shift_i] / 10;
|
|
shift_low = x->lsu[shift_i] % 10;
|
|
x->lsu[shift_i] = shift_high + ( shift_old*10);
|
|
shift_old = shift_low;
|
|
}
|
|
}
|
|
|
|
static void shift_left(dec80* x){
|
|
shift_high = shift_low = shift_old = 0;
|
|
for (shift_i = DEC80_NUM_LSU - 1; shift_i < 255; shift_i--){
|
|
shift_high = x->lsu[shift_i] / 10;
|
|
shift_low = x->lsu[shift_i] % 10;
|
|
x->lsu[shift_i] = shift_old + ( shift_low*10);
|
|
shift_old = shift_high;
|
|
}
|
|
}
|
|
|
|
static void remove_leading_zeros(dec80* x){
|
|
uint8_t digit100;
|
|
uint8_t is_negative = (x->exponent < 0);
|
|
exp_t exponent = get_exponent(x);
|
|
|
|
stack_debug(0x02);
|
|
//find first non-zero digit100
|
|
for (digit100 = 0; digit100 < DEC80_NUM_LSU; digit100++){
|
|
if (x->lsu[digit100] != 0){
|
|
break;
|
|
}
|
|
}
|
|
exponent -= digit100 * 2; //base 100
|
|
|
|
//copy digit100s left if needed
|
|
if (digit100 != 0 && digit100 < DEC80_NUM_LSU){ //found non-zero digit100
|
|
uint8_t i;
|
|
//copy digit100s
|
|
for (i = 0; digit100 < DEC80_NUM_LSU; i++, digit100++){
|
|
x->lsu[i] = x->lsu[digit100];
|
|
}
|
|
//zero out remaining digit100s, now that number left-aligned
|
|
zero_remaining_dec80(x, i);
|
|
}
|
|
|
|
//ensure MSdigit in MSdigit100 is > 0
|
|
if (x->lsu[0] < 10) {
|
|
shift_left(x);
|
|
exponent--;
|
|
}
|
|
//write back exponent
|
|
set_exponent(x, exponent, is_negative);
|
|
}
|
|
|
|
void build_dec80(__xdata const char* signif_str, __xdata exp_t exponent){
|
|
enum {
|
|
SIGN_ZERO,
|
|
SIGN_ZERO_SEEN_POINT,
|
|
SIGN_NEG_ZERO,
|
|
SIGN_NEG_ZERO_SEEN_POINT,
|
|
SIGN_POS,
|
|
SIGN_POS_SEEN_POINT,
|
|
SIGN_NEG,
|
|
SIGN_NEG_SEEN_POINT
|
|
};
|
|
#ifdef DESKTOP
|
|
static_assert(SIGN_ZERO < SIGN_NEG_ZERO, "do not change enum values");
|
|
#endif
|
|
#define SEEN_POINT(curr_sign) ((curr_sign) & 1)
|
|
#define IS_ZERO(curr_sign) ((curr_sign) <= SIGN_NEG_ZERO_SEEN_POINT)
|
|
#define IS_NEG(curr_sign) ((curr_sign) >= SIGN_NEG)
|
|
|
|
uint8_t i = 0;
|
|
uint8_t nibble_i = 0;
|
|
uint8_t save_nibble = 0;
|
|
int8_t num_lr_points = 0; //number of digits to the right (-) or left of decimal point
|
|
int8_t curr_sign = SIGN_ZERO;
|
|
|
|
//check first digit
|
|
if (signif_str[0] == '\0'){
|
|
set_dec80_zero(&AccDecn);
|
|
return;
|
|
} else if (signif_str[0] == '-'){
|
|
curr_sign = SIGN_NEG_ZERO;
|
|
i++;
|
|
}
|
|
|
|
//go through digits
|
|
while (1){
|
|
if (signif_str[i] == '.'){
|
|
if (!SEEN_POINT(curr_sign)){
|
|
//begin tracking number of digits to right of decimal point
|
|
curr_sign |= 1; //seen point
|
|
}
|
|
#ifdef EXTRA_CHECKS
|
|
else {
|
|
//multiple '.'s in string
|
|
#ifdef DEBUG
|
|
printf(" ERROR: multiple '.'s in string\n");
|
|
#endif
|
|
set_dec80_NaN(&AccDecn);
|
|
return;
|
|
}
|
|
#endif
|
|
} else if (signif_str[i] >= '1' && signif_str[i] <= '9'){
|
|
if (nibble_i < DEC80_NUM_LSU*2){
|
|
if (nibble_i & 1) { //odd
|
|
AccDecn.lsu[nibble_i/2] = save_nibble * 10 + (signif_str[i] - '0');
|
|
} else {
|
|
save_nibble = signif_str[i] - '0';
|
|
}
|
|
}
|
|
nibble_i++;
|
|
//track number of digits R of decimal point
|
|
//must do this before changing is_zero
|
|
if (num_lr_points == 0 &&
|
|
(curr_sign == SIGN_ZERO_SEEN_POINT || curr_sign == SIGN_NEG_ZERO_SEEN_POINT))
|
|
{
|
|
num_lr_points = -1;
|
|
}
|
|
//track sign
|
|
if (curr_sign == SIGN_ZERO){
|
|
curr_sign = SIGN_POS;
|
|
} else if (curr_sign == SIGN_ZERO_SEEN_POINT){
|
|
curr_sign = SIGN_POS_SEEN_POINT;
|
|
} else if (curr_sign == SIGN_NEG_ZERO){
|
|
curr_sign = SIGN_NEG;
|
|
} else if (curr_sign == SIGN_NEG_ZERO_SEEN_POINT){
|
|
curr_sign = SIGN_NEG_SEEN_POINT;
|
|
}
|
|
//track number digits L of decimal point
|
|
if (!SEEN_POINT(curr_sign)){ //haven't seen decimal point yet
|
|
num_lr_points++; //increase left count
|
|
}
|
|
} else if (signif_str[i] == '0'){
|
|
//make sure not a leading zero
|
|
if (!IS_ZERO(curr_sign)){ //non-zero value
|
|
if (nibble_i < DEC80_NUM_LSU*2){
|
|
if (nibble_i & 1) { //odd
|
|
AccDecn.lsu[nibble_i/2] = save_nibble * 10 + 0;
|
|
} else {
|
|
save_nibble = 0;
|
|
}
|
|
}
|
|
nibble_i++;
|
|
}
|
|
//track number digits L/R of decimal point
|
|
if (SEEN_POINT(curr_sign)){
|
|
if (IS_ZERO(curr_sign)){ //tracking 0s to right of point
|
|
if (num_lr_points == 0){ //no left count exists
|
|
num_lr_points = -2;
|
|
} else if (num_lr_points < 0){ //continue tracking count
|
|
num_lr_points--; //increase right count
|
|
}
|
|
}
|
|
} else { //haven't seen decimal point yet
|
|
if (!IS_ZERO(curr_sign)){ //not a leading zero
|
|
num_lr_points++; //increase left count
|
|
}
|
|
}
|
|
} else if (signif_str[i] == '\0'){ //done
|
|
if (IS_ZERO(curr_sign)){
|
|
//zero
|
|
set_dec80_zero(&AccDecn);
|
|
return;
|
|
} else {
|
|
//not zero
|
|
exp_t new_exponent = exponent;
|
|
//write out saved nibble, if it exists
|
|
// (saved while nibble_i even, nibble_i then incremented to odd)
|
|
if (nibble_i & 1){ //odd
|
|
AccDecn.lsu[nibble_i/2] = save_nibble * 10;
|
|
nibble_i++; //increment for zeroing out
|
|
}
|
|
//zero out any old data
|
|
zero_remaining_dec80(&AccDecn, nibble_i/2);
|
|
// adjust exponent for left-aligned significand input
|
|
// or for number of digits past decimal point
|
|
if (num_lr_points > 0){ //left count exists
|
|
new_exponent = exponent + (num_lr_points - 1); //1 digit left of implicit point
|
|
//overflow is checked later, should be impossible to overflow int16_t:
|
|
assert(new_exponent >= exponent);
|
|
} else if (num_lr_points < 0) { //right count exists
|
|
// (num_lr_points represents exponent shift)
|
|
// (this ends up being a subtraction)
|
|
new_exponent = exponent + num_lr_points;
|
|
//underflow is checked later, should be impossible to overflow int16_t:
|
|
assert(new_exponent <= exponent);
|
|
}
|
|
|
|
//check for over/underflow of exponent
|
|
exponent = new_exponent;
|
|
#ifdef EXTRA_CHECKS
|
|
if (exponent > DEC80_MAX_EXP || exponent < DEC80_MIN_EXP){
|
|
#ifdef DEBUG
|
|
printf(" over/underflow (new_exp, exp)=(%d,%d)\n",
|
|
new_exponent, exponent);
|
|
#endif
|
|
set_dec80_NaN(&AccDecn);
|
|
return;
|
|
}
|
|
#endif
|
|
//set negative bit
|
|
set_exponent(&AccDecn, exponent, IS_NEG(curr_sign));
|
|
//normalize
|
|
remove_leading_zeros(&AccDecn);
|
|
#ifdef DEBUG
|
|
printf(" num_lr_points (%d), new_exp (%d), sign (%d), exp (%d)\n",
|
|
num_lr_points, new_exponent, curr_sign, exponent);
|
|
printf(" ");
|
|
for (i = 0; i < DEC80_NUM_LSU; i++){
|
|
printf("%02d,", AccDecn.lsu[i]);
|
|
}
|
|
printf("\n");
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
} else { //invalid character
|
|
#ifdef DEBUG
|
|
printf(" invalid character %c at i=%d\n", signif_str[i], i);
|
|
#endif
|
|
set_dec80_NaN(&AccDecn);
|
|
return;
|
|
}
|
|
i++;
|
|
assert(i < DECN_BUF_SIZE);
|
|
} // while 1
|
|
#undef SEEN_POINT
|
|
#undef IS_ZERO
|
|
#undef IS_NEG
|
|
}
|
|
|
|
|
|
|
|
void set_dec80_zero(dec80* dest){
|
|
uint8_t i;
|
|
|
|
//clear exponent
|
|
dest->exponent = 0;
|
|
//clear nibbles
|
|
for (i = 0; i < DEC80_NUM_LSU; i++){
|
|
dest->lsu[i] = 0;
|
|
}
|
|
}
|
|
|
|
uint8_t decn_is_zero(const dec80* x){
|
|
uint8_t i;
|
|
for (i = 0; i < DEC80_NUM_LSU; i++){
|
|
if (x->lsu[i] != 0){
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
uint8_t decn_is_nan(const dec80* x){
|
|
uint8_t i;
|
|
if (x->exponent != DEC80_NAN_EXP){
|
|
return 0;
|
|
}
|
|
for (i = 0; i < DEC80_NUM_LSU; i++){
|
|
if (x->lsu[i] != 0xff){
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
#ifdef EXTRA_CHECKS
|
|
void set_dec80_NaN(dec80* dest){
|
|
uint8_t i;
|
|
|
|
//set exponent to special val
|
|
dest->exponent = DEC80_NAN_EXP;
|
|
//set all nibbles
|
|
for (i = 0; i < DEC80_NUM_LSU; i++){
|
|
dest->lsu[i] = 0xff;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void negate_decn(dec80* x){
|
|
#ifdef EXP16
|
|
static const exp_t xor_val = -(0x7fff) - 1;
|
|
#else
|
|
static const exp_t xor_val = -(0x7f) - 1;
|
|
#endif
|
|
if (decn_is_nan(x)){
|
|
return;
|
|
}
|
|
(x->exponent) ^= xor_val;
|
|
}
|
|
|
|
static int8_t compare_magn(void){ //returns a<b: -1, a==b: 0, a>b: 1
|
|
uint8_t a_i, b_i;
|
|
exp_t a_exp=0, b_exp=0;
|
|
int8_t a_signif_b = 0; //a<b: -1, a==b: 0, a>b: 1
|
|
static __xdata dec80 a_tmp, b_tmp;
|
|
//copy
|
|
copy_decn(&a_tmp, &AccDecn);
|
|
copy_decn(&b_tmp, &BDecn);
|
|
//normalize
|
|
remove_leading_zeros(&a_tmp);
|
|
remove_leading_zeros(&b_tmp);
|
|
//compare signifcands while tracking magnitude
|
|
for (
|
|
a_i = 0, b_i = 0;
|
|
a_i < DEC80_NUM_LSU;
|
|
a_i++, b_i++, a_exp+=2, b_exp+=2
|
|
)
|
|
{
|
|
//set signif. inequality if this is first digit that is different
|
|
if (a_signif_b == 0 && (a_tmp.lsu[a_i] < b_tmp.lsu[b_i])){
|
|
#ifdef DEBUG_COMPARE_MAGN
|
|
printf("a_signif_b -1: a.lsu[%d]=%d, b.lsu[%d]=%d\n",
|
|
a_i, a_tmp.lsu[a_i], b_i, b_tmp.lsu[b_i]);
|
|
#endif
|
|
a_signif_b = -1;
|
|
} else if (a_signif_b == 0 && (a_tmp.lsu[a_i] > b_tmp.lsu[b_i])){
|
|
#ifdef DEBUG_COMPARE_MAGN
|
|
printf("a_signif_b 1: a.lsu[%d]=%d, b.lsu[%d]=%d\n",
|
|
a_i, a_tmp.lsu[a_i], b_i, b_tmp.lsu[b_i]);
|
|
#endif
|
|
a_signif_b = 1;
|
|
}
|
|
}
|
|
//calculate exponents
|
|
a_exp += get_exponent(&a_tmp);
|
|
b_exp += get_exponent(&b_tmp);
|
|
//compare exponents
|
|
if (a_exp > b_exp){
|
|
#ifdef DEBUG
|
|
printf("a_exp > b_exp\n");
|
|
#endif
|
|
return 1;
|
|
} else if (a_exp < b_exp){
|
|
#ifdef DEBUG
|
|
printf("a_exp < b_exp\n");
|
|
#endif
|
|
return -1;
|
|
}
|
|
//exponents equal, compare by significand
|
|
#ifdef DEBUG
|
|
printf("a_signif_b (%d)\n", a_signif_b);
|
|
#endif
|
|
return a_signif_b;
|
|
}
|
|
|
|
#ifdef USE_COMPARE_DECN
|
|
static int8_t compare_decn(void){ //returns a<b: -1, a==b: 0, a>b: 1
|
|
int8_t is_neg;
|
|
|
|
//handle zero special cases
|
|
if (decn_is_zero(&AccDecn) && decn_is_zero(&BDecn)){
|
|
return 0;
|
|
} else if (decn_is_zero(&AccDecn)){
|
|
if (BDecn.exponent < 0){
|
|
return 1;
|
|
} else {
|
|
return -1;
|
|
}
|
|
} else if (decn_is_zero(&BDecn)){
|
|
if (AccDecn.exponent < 0){
|
|
return -1;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
//handle cases where signs differ
|
|
if (AccDecn.exponent < 0 && BDecn.exponent > 0){
|
|
return -1;
|
|
} else if (AccDecn.exponent > 0 && BDecn.exponent < 0){
|
|
return 1;
|
|
}
|
|
//signs must now be the same, either both pos, or both neg
|
|
is_neg = (AccDecn.exponent < 0 ? -1 : 1);
|
|
|
|
return is_neg * compare_magn();
|
|
}
|
|
#endif //USE_COMPARE_DECN
|
|
|
|
//WARNING: for add_decn() and sub_mag() functions only
|
|
//acc and the number from which exponent was taken MUST be stripped of leading 0s first
|
|
//rescales acc up to exponent (increase exponent of acc, while shifting right)
|
|
//the actual value of acc->exponent remains unchanged
|
|
static void _incr_exp(dec80* acc, exp_t exponent){
|
|
exp_t curr_exp = get_exponent(acc);
|
|
#ifdef DEBUG_ADD
|
|
uint8_t is_neg = (acc->exponent < 0);
|
|
printf(" (is_neg,curr_exp,exponent)=(%d,%d,%d)\n",
|
|
is_neg, curr_exp, exponent);
|
|
#endif
|
|
assert(exponent > curr_exp);
|
|
while (curr_exp != exponent){
|
|
//shift right
|
|
shift_right(acc);
|
|
curr_exp++;
|
|
}
|
|
|
|
//curr_exp does NOT get written back to acc->exponent
|
|
}
|
|
|
|
//for internal use only,
|
|
//AccDecn must be larger than BDecn in absolute value
|
|
//subtract by equal addition algorithm
|
|
static void sub_mag(){
|
|
int8_t i;
|
|
uint8_t carry = 0;
|
|
//normalize
|
|
remove_leading_zeros(&AccDecn);
|
|
remove_leading_zeros(&BDecn);
|
|
if (get_exponent(&AccDecn) != get_exponent(&BDecn)){
|
|
_incr_exp(&BDecn, get_exponent(&AccDecn));
|
|
}
|
|
#ifdef DEBUG_ADD
|
|
decn_to_str_complete(&tmp);
|
|
printf(" incr_exp tmp: %s\n", Buf);
|
|
#endif
|
|
//do subtraction
|
|
for (i = DEC80_NUM_LSU - 1; i >=0; i--){
|
|
uint8_t digit100;
|
|
if (AccDecn.lsu[i] >= (BDecn.lsu[i] + carry)){
|
|
digit100 = AccDecn.lsu[i] - (BDecn.lsu[i] + carry);
|
|
carry = 0;
|
|
} else {
|
|
digit100 = AccDecn.lsu[i] + 100 - (BDecn.lsu[i] + carry);
|
|
carry = 1;
|
|
}
|
|
assert(digit100 < 100);
|
|
AccDecn.lsu[i] = digit100;
|
|
}
|
|
assert(carry == 0); //shouldn't be carry out if |acc| > |x|
|
|
}
|
|
|
|
void add_decn(void){
|
|
int8_t rel;
|
|
uint8_t carry = 0;
|
|
int8_t i;
|
|
|
|
//check if zero
|
|
if (decn_is_zero(&BDecn)){
|
|
return;
|
|
} else if (decn_is_zero(&AccDecn)){
|
|
copy_decn(&AccDecn, &BDecn);
|
|
return;
|
|
}
|
|
//save b for restoring later
|
|
//n.b. don't use TmpStackDecn here, it is called quite often. So you'd need to increase TMP_STACK_SIZE
|
|
copy_decn(&TmpDecn, &BDecn);
|
|
//handle cases where signs differ
|
|
if (AccDecn.exponent < 0 && BDecn.exponent >= 0){
|
|
// -acc, +x
|
|
rel = compare_magn();
|
|
if (rel == 1){
|
|
#ifdef DEBUG_ADD
|
|
printf("|-acc| > |+x|\n");
|
|
#endif
|
|
sub_mag();
|
|
} else if (rel == -1){
|
|
#ifdef DEBUG_ADD
|
|
printf("|-acc| < |+x|\n");
|
|
#endif
|
|
copy_decn(&BDecn, &AccDecn);
|
|
copy_decn(&AccDecn, &TmpDecn);
|
|
sub_mag();
|
|
} else { //equal
|
|
#ifdef DEBUG_ADD
|
|
printf("|-acc| == |+x|\n");
|
|
#endif
|
|
set_dec80_zero(&AccDecn);
|
|
}
|
|
//restore b
|
|
copy_decn(&BDecn, &TmpDecn);
|
|
|
|
return;
|
|
} else if (AccDecn.exponent >= 0 && BDecn.exponent < 0){
|
|
// +acc, -x
|
|
rel = compare_magn();
|
|
if (rel == 1){
|
|
#ifdef DEBUG_ADD
|
|
printf("|+acc| > |-x|\n");
|
|
#endif
|
|
sub_mag();
|
|
} else if (rel == -1){
|
|
#ifdef DEBUG_ADD
|
|
printf("|+acc| < |-x|\n");
|
|
#endif
|
|
copy_decn(&BDecn, &AccDecn);
|
|
copy_decn(&AccDecn, &TmpDecn);
|
|
sub_mag();
|
|
} else { //equal
|
|
#ifdef DEBUG_ADD
|
|
printf("|+acc| == |-x|\n");
|
|
#endif
|
|
set_dec80_zero(&AccDecn);
|
|
}
|
|
//restore b
|
|
copy_decn(&BDecn, &TmpDecn);
|
|
|
|
return;
|
|
}
|
|
//signs must now be the same, begin adding
|
|
//normalize
|
|
remove_leading_zeros(&AccDecn);
|
|
remove_leading_zeros(&BDecn);
|
|
#ifdef DEBUG_ADD
|
|
decn_to_str_complete(&AccDecn);
|
|
printf(" rem_leading_zeros acc: %s\n", Buf);
|
|
decn_to_str_complete(&BDecn);
|
|
printf(" rem_leading_zeros tmp: %s\n", Buf);
|
|
#endif
|
|
if (get_exponent(&AccDecn) > get_exponent(&BDecn)){
|
|
_incr_exp(&BDecn, get_exponent(&AccDecn));
|
|
} else if (get_exponent(&AccDecn) < get_exponent(&BDecn)){
|
|
//shift significand and adjust exponent to match
|
|
for (i = 0; i < get_exponent(&BDecn) - get_exponent(&AccDecn); i++){
|
|
shift_right(&AccDecn);
|
|
}
|
|
set_exponent(&AccDecn, get_exponent(&BDecn), (AccDecn.exponent < 0));
|
|
}
|
|
#ifdef DEBUG_ADD
|
|
decn_to_str_complete(&AccDecn);
|
|
printf(" incr_exp acc: %s\n", Buf);
|
|
decn_to_str_complete(&BDecn);
|
|
printf(" incr_exp tmp: %s\n", Buf);
|
|
#endif
|
|
//do addition
|
|
for (i = DEC80_NUM_LSU - 1; i >= 0; i--){
|
|
uint8_t digit100 = AccDecn.lsu[i] + BDecn.lsu[i] + carry;
|
|
AccDecn.lsu[i] = digit100 % 100;
|
|
carry = digit100 / 100;
|
|
assert(carry <= 1);
|
|
}
|
|
//may need to rescale number
|
|
if (carry > 0){
|
|
exp_t curr_exp = get_exponent(&AccDecn);
|
|
assert(carry == 1);
|
|
rel = (AccDecn.exponent < 0); //is_neg?
|
|
//shift right
|
|
shift_right(&AccDecn);
|
|
AccDecn.lsu[0] += 10; //carry gets shifted into most significant digit
|
|
curr_exp++;
|
|
//track sign
|
|
set_exponent(&AccDecn, curr_exp, rel); //rel==is_neg?
|
|
}
|
|
|
|
//restore b
|
|
copy_decn(&BDecn, &TmpDecn);
|
|
}
|
|
|
|
//AccDecn *= BDecn
|
|
// (the BDecn register is preserved)
|
|
void mult_decn(void){
|
|
int8_t i, j;
|
|
uint8_t carry = 0;
|
|
uint8_t is_neg;
|
|
exp_t new_exponent;
|
|
#ifdef EXTRA_CHECKS
|
|
if (decn_is_nan(&AccDecn) || decn_is_nan(&BDecn)) {
|
|
set_dec80_NaN(&AccDecn);
|
|
return;
|
|
}
|
|
#endif
|
|
//initialize values
|
|
set_dec80_zero(&TmpDecn);
|
|
//normalize
|
|
remove_leading_zeros(&AccDecn);
|
|
remove_leading_zeros(&BDecn);
|
|
//store new sign
|
|
#ifdef EXP16
|
|
if ((AccDecn.exponent & 0x8000) ^ (BDecn.exponent & 0x8000)){ //signs differ
|
|
#else
|
|
if ((AccDecn.exponent & 0x80) ^ (BDecn.exponent & 0x80)){ //signs differ
|
|
#endif
|
|
is_neg = 1;
|
|
} else {
|
|
is_neg = 0;
|
|
}
|
|
//calculate new exponent
|
|
new_exponent = get_exponent(&AccDecn) + get_exponent(&BDecn);
|
|
#ifdef DEBUG_MULT
|
|
printf("\n a_exp: %d, b_exp: %d", get_exponent(&AccDecn), get_exponent(&BDecn));
|
|
printf("\n new exponent: %d, is_neg: %u", new_exponent, is_neg);
|
|
#endif
|
|
//do multiply
|
|
for (i = DEC80_NUM_LSU - 1; i >= 0; i--){
|
|
//partial product
|
|
for (j = DEC80_NUM_LSU - 1; j >= 0; j--){
|
|
uint16_t digit100 = TmpDecn.lsu[j] + (BDecn.lsu[i] * AccDecn.lsu[j]) + carry;
|
|
TmpDecn.lsu[j] = digit100 % 100;
|
|
carry = digit100 / 100;
|
|
assert(carry < 100);
|
|
}
|
|
#ifdef DEBUG_MULT_ALL
|
|
printf("\n%d:", i);
|
|
printf("\n acc:");
|
|
for (j = 0; j < DEC80_NUM_LSU; j++){
|
|
printf(" %3d", AccDecn.lsu[j]);
|
|
}
|
|
printf("\n x:");
|
|
for (j = 0; j < DEC80_NUM_LSU; j++){
|
|
if (j == i)
|
|
printf(" %3d", BDecn.lsu[j]);
|
|
else
|
|
printf(" ");
|
|
}
|
|
#endif
|
|
#ifdef DEBUG_MULT
|
|
printf("\n TmpDecn:");
|
|
for (j = 0; j < DEC80_NUM_LSU; j++){
|
|
printf(" %3d", TmpDecn.lsu[j]);
|
|
}
|
|
printf("\ncarry:%d", carry);
|
|
#endif
|
|
if (i != 0){ //handle last carry separately later, no final shift
|
|
//shift
|
|
shift_right(&TmpDecn);
|
|
shift_right(&TmpDecn);
|
|
//add back carry to MSdigit100
|
|
TmpDecn.lsu[0] = carry; //was 0 from shift
|
|
}
|
|
}
|
|
//handle last carry
|
|
if (carry >= 10){
|
|
//shift
|
|
shift_right(&TmpDecn);
|
|
shift_right(&TmpDecn);
|
|
new_exponent += 1;
|
|
//add back carry to MSdigit100
|
|
TmpDecn.lsu[0] = carry; //was 0 from shift
|
|
} else if (carry > 0){
|
|
//shift
|
|
shift_right(&TmpDecn);
|
|
//add back carry to MSdigit in MSdigit100
|
|
TmpDecn.lsu[0] += carry*10;
|
|
}
|
|
//set new exponent, checking for over/underflow
|
|
#ifdef DEBUG_MULT
|
|
printf("\n new exponent: %d, is_neg: %u\n", new_exponent, is_neg);
|
|
#endif
|
|
if (new_exponent < DEC80_MAX_EXP && new_exponent > DEC80_MIN_EXP){
|
|
set_exponent(&TmpDecn, new_exponent, is_neg);
|
|
} else {
|
|
set_dec80_NaN(&AccDecn);
|
|
return;
|
|
}
|
|
//copy back to acc
|
|
copy_decn(&AccDecn, &TmpDecn);
|
|
//normalize
|
|
remove_leading_zeros(&AccDecn);
|
|
}
|
|
|
|
void recip_decn(void){
|
|
#define CURR_RECIP Tmp2Decn //copy of x, holds current 1/x estimate
|
|
uint8_t i;
|
|
exp_t initial_exp;
|
|
//check divide by zero
|
|
#ifdef EXTRA_CHECKS
|
|
if (decn_is_zero(&AccDecn)){
|
|
set_dec80_NaN(&AccDecn);
|
|
#ifdef DESKTOP
|
|
printf("error division by 0\n");
|
|
#endif
|
|
return;
|
|
}
|
|
#endif
|
|
//normalize
|
|
remove_leading_zeros(&AccDecn);
|
|
//store copy of x
|
|
st_push_decn(&AccDecn);
|
|
//get initial exponent of estimate for 1/x
|
|
initial_exp = get_exponent(&AccDecn);
|
|
#ifdef DEBUG_DIV
|
|
printf("exponent %d", initial_exp);
|
|
#endif
|
|
//necessary to subtract 1 for convergence
|
|
initial_exp = -initial_exp - 1;
|
|
#ifdef DEBUG_DIV
|
|
printf(" -> %d\n", initial_exp);
|
|
#endif
|
|
set_exponent(&CURR_RECIP, initial_exp, (AccDecn.exponent < 0)); //set exponent, copy sign
|
|
//get initial estimate for 1/x
|
|
if (AccDecn.lsu[0] < 20){ //mantissa between 1 and 2
|
|
CURR_RECIP.lsu[0] = 50; //0.50 with implicit point and exponent
|
|
} else if (AccDecn.lsu[0] < 33){
|
|
CURR_RECIP.lsu[0] = 30;
|
|
} else if (AccDecn.lsu[0] < 50){
|
|
CURR_RECIP.lsu[0] = 20;
|
|
} else {
|
|
CURR_RECIP.lsu[0] = 10; //0.1 with implicit point and exponent
|
|
}
|
|
for (i = 1; i < DEC80_NUM_LSU; i++){
|
|
CURR_RECIP.lsu[i] = 0;
|
|
}
|
|
copy_decn(&AccDecn, &CURR_RECIP);
|
|
//do newton-raphson iterations
|
|
for (i = 0; i < 6; i++){ //just fix number of iterations for now
|
|
#ifdef DEBUG_DIV
|
|
decn_to_str_complete(&CURR_RECIP);
|
|
printf("%2d: %s\n", i, Buf);
|
|
#endif
|
|
//Accum *= x_copy
|
|
st_load_decn(&BDecn);
|
|
mult_decn();
|
|
#ifdef DEBUG_DIV
|
|
decn_to_str_complete(&AccDecn);
|
|
printf(" %20s: %s\n", "recip*x", Buf);
|
|
#endif
|
|
//Accum *= -1
|
|
negate_decn(&AccDecn);
|
|
//Accum += 1
|
|
copy_decn(&BDecn, &DECN_1);
|
|
add_decn();
|
|
#ifdef DEBUG_DIV
|
|
decn_to_str_complete(&AccDecn);
|
|
printf(" %20s: %s\n", "(1-recip*x)", Buf);
|
|
#endif
|
|
//Accum *= curr_recip
|
|
copy_decn(&BDecn, &CURR_RECIP);
|
|
mult_decn();
|
|
#ifdef DEBUG_DIV
|
|
decn_to_str_complete(&AccDecn);
|
|
printf(" %20s: %s\n", "recip * (1-recip*x)", Buf);
|
|
#endif
|
|
//Accum += curr_recip
|
|
add_decn();
|
|
//new_est(Accum) = recip + (1 - recip*x)*recip, where recip is current recip estimate
|
|
copy_decn(&CURR_RECIP, &AccDecn);
|
|
}
|
|
st_pop_decn(0);
|
|
|
|
//try not to pollute namespace
|
|
#undef CURR_RECIP
|
|
}
|
|
|
|
void div_decn(void){
|
|
//store copy of acc for final multiply by 1/x
|
|
st_push_decn(&AccDecn);
|
|
copy_decn(&AccDecn, &BDecn);
|
|
recip_decn();
|
|
//Accum now holds 1/x, multiply by original acc to complete division
|
|
st_pop_decn(&BDecn);
|
|
mult_decn();
|
|
}
|
|
|
|
|
|
|
|
//constants used for ln(x) and exp(x)
|
|
#define NUM_A_ARR 9
|
|
static const dec80 LN_A_ARR[NUM_A_ARR] = {
|
|
{-1 & 0x7fff, {69, 31, 47, 18, 5, 59, 94, 53, 9}},
|
|
{-2 & 0x7fff, {95, 31, 1, 79, 80, 43, 24, 86, 0}},
|
|
{-3 & 0x7fff, {99, 50, 33, 8, 53, 16, 80, 82, 84}},
|
|
{-4 & 0x7fff, {99, 95, 0, 33, 30, 83, 53, 31, 67}},
|
|
{-5 & 0x7fff, {99, 99, 50, 0, 33, 33, 8, 33, 33}},
|
|
{-6 & 0x7fff, {99, 99, 95, 0, 0, 33, 33, 29, 95}},
|
|
{-7 & 0x7fff, {99, 99, 99, 50, 0, 0, 33, 5, 35}},
|
|
{-8 & 0x7fff, {99, 99, 99, 95, 0, 0, 2, 76, 40}},
|
|
{-9 & 0x7fff, {99, 99, 99, 99, 50, 0, 15, 98, 65}},
|
|
};
|
|
|
|
void ln_decn(void){
|
|
uint8_t j, k;
|
|
#define B_j Tmp2Decn
|
|
#define NUM_TIMES Tmp3Decn
|
|
|
|
//check not negative or zero
|
|
if (AccDecn.exponent < 0 || decn_is_zero(&AccDecn)){
|
|
set_dec80_NaN(&AccDecn);
|
|
return;
|
|
}
|
|
//normalize
|
|
remove_leading_zeros(&AccDecn);
|
|
//scale to between 1 and 10
|
|
NUM_TIMES.exponent = get_exponent(&AccDecn) + 1; //store initial exp in NUM_TIMES.exponent
|
|
AccDecn.exponent = 0;
|
|
#ifdef DEBUG_LOG
|
|
decn_to_str_complete(&AccDecn);
|
|
printf("ln() accum scaled between 1,10: %s\n", Buf);
|
|
#endif
|
|
//get initial estimate (accum = 10 - A)
|
|
copy_decn(&BDecn, &DECN_1);
|
|
BDecn.exponent = 1; //BDecn = 10
|
|
negate_decn(&AccDecn);
|
|
add_decn();
|
|
copy_decn(&B_j, &AccDecn); //b_j = accum = 10 - A
|
|
#ifdef DEBUG_LOG
|
|
decn_to_str_complete(&AccDecn);
|
|
printf("ln() initial accum: %s (%d)\n", Buf, NUM_TIMES.exponent);
|
|
#endif
|
|
|
|
//track number of times multiplied by a_arr[j]
|
|
for (j = 0; j < NUM_A_ARR; j++){
|
|
uint8_t k_j;
|
|
if (j != 0){
|
|
//b_j *= 10.0
|
|
shift_left(&B_j);
|
|
}
|
|
copy_decn(&AccDecn, &B_j); //accum = b_j
|
|
k_j = 0;
|
|
while (!(AccDecn.exponent < 0)){ //while >= 0 (!negative) TODO: should just be >
|
|
uint8_t i;
|
|
copy_decn(&B_j, &AccDecn); //b_j = accum
|
|
//accum *= a_arr[j]
|
|
// since a_arr[j] is of the form 1 + 10^-j, just shift accum and add to self
|
|
copy_decn(&BDecn, &AccDecn);
|
|
for (i = 0; i < j; i++){
|
|
shift_right(&BDecn);
|
|
}
|
|
add_decn();
|
|
//accum -= 10
|
|
// accum.exponent is 1 while needs subtracting
|
|
if (AccDecn.lsu[0] >= 10 && get_exponent(&AccDecn) > 0){
|
|
AccDecn.lsu[0] -= 10;
|
|
} else {
|
|
//set as negative to get out of while(), accum will get overwritten with b_j
|
|
AccDecn.exponent = -1;
|
|
}
|
|
#ifdef DEBUG_LOG_ALL
|
|
decn_to_str_complete(&AccDecn);
|
|
printf(" %u: %s\t", k_j, Buf);
|
|
for (int ii = 0; ii < DEC80_NUM_LSU; ii++){
|
|
printf("%2d ", AccDecn.lsu[ii]);
|
|
}
|
|
printf(" (%d)\n", get_exponent(&AccDecn));
|
|
#endif
|
|
k_j++;
|
|
#ifdef DEBUG_LOG
|
|
assert(k_j != 255);
|
|
#endif
|
|
}
|
|
//track num times
|
|
NUM_TIMES.lsu[j] = k_j - 1;
|
|
#ifdef DEBUG_LOG
|
|
decn_to_str_complete(&B_j);
|
|
printf(" %u: num_times: %u, %s\n", j, NUM_TIMES.lsu[j], Buf);
|
|
#endif
|
|
}
|
|
|
|
//build final value
|
|
copy_decn(&AccDecn, &B_j); //remainder
|
|
#ifdef DEBUG_LOG
|
|
decn_to_str_complete(&AccDecn);
|
|
printf("ln() remainder: %s\n", Buf);
|
|
#endif
|
|
for (j = NUM_A_ARR - 1; j < NUM_A_ARR; j--){ //sum in reverse order, note: (j < NUM_A_ARR) == signed(j >= 0)
|
|
for (k = 0; k < NUM_TIMES.lsu[j]; k++){
|
|
//accum += ln_a_arr[j];
|
|
copy_decn(&BDecn, &LN_A_ARR[j]);
|
|
BDecn.exponent = 0; //tracking exponent through shifts/saved initial exponent
|
|
add_decn();
|
|
}
|
|
shift_right(&AccDecn);
|
|
#ifdef DEBUG_LOG
|
|
decn_to_str_complete(&AccDecn);
|
|
printf("%u: ln() remainder: %s\n", j, Buf);
|
|
#endif
|
|
}
|
|
#ifdef DEBUG_LOG
|
|
decn_to_str_complete(&AccDecn);
|
|
printf("ln() accum after summing: %s\n", Buf);
|
|
#endif
|
|
//accum = -accum;
|
|
negate_decn(&AccDecn);
|
|
|
|
//add back in initial exponent (stored in NUM_TIMES.exponent)
|
|
copy_decn(&B_j, &AccDecn); //temporarily store accum in B_j
|
|
set_dec80_zero(&AccDecn);
|
|
//check if negative
|
|
if (NUM_TIMES.exponent < 0){
|
|
j = 1; //track is_neg
|
|
NUM_TIMES.exponent = -NUM_TIMES.exponent;
|
|
} else {
|
|
j = 0;
|
|
}
|
|
//check if too big for single lsu
|
|
#ifdef EXP16
|
|
if (NUM_TIMES.exponent >= 10000){
|
|
AccDecn.lsu[0] = NUM_TIMES.exponent / 10000;
|
|
NUM_TIMES.exponent = NUM_TIMES.exponent % 10000;
|
|
AccDecn.lsu[1] = NUM_TIMES.exponent / 100;
|
|
AccDecn.lsu[2] = NUM_TIMES.exponent % 100;
|
|
AccDecn.exponent = 5;
|
|
} else
|
|
#endif
|
|
if (NUM_TIMES.exponent >= 100){
|
|
AccDecn.lsu[0] = NUM_TIMES.exponent / 100;
|
|
AccDecn.lsu[1] = NUM_TIMES.exponent % 100;
|
|
AccDecn.exponent = 3;
|
|
} else {
|
|
AccDecn.lsu[0] = NUM_TIMES.exponent;
|
|
AccDecn.exponent = 1;
|
|
}
|
|
//check if need to negate
|
|
if (j) { //was negative
|
|
negate_decn(&AccDecn);
|
|
}
|
|
#ifdef DEBUG_LOG
|
|
decn_to_str_complete(&AccDecn);
|
|
printf("ln() exponent from initial: %s\n", Buf);
|
|
#endif
|
|
//initial exp * ln(10)
|
|
copy_decn(&BDecn, &DECN_LN_10);
|
|
mult_decn();
|
|
//add back stored accum
|
|
copy_decn(&BDecn, &B_j);
|
|
add_decn();
|
|
|
|
//try not to pollute namespace
|
|
#undef B_j
|
|
#undef NUM_TIMES
|
|
}
|
|
|
|
void log10_decn(void){
|
|
ln_decn();
|
|
copy_decn(&BDecn, &DECN_LN_10);
|
|
div_decn();
|
|
}
|
|
|
|
|
|
|
|
void exp_decn(void){
|
|
uint8_t j, k;
|
|
uint8_t need_recip = 0;
|
|
#define SAVED Tmp2Decn
|
|
#define NUM_TIMES Tmp3Decn
|
|
|
|
//check not error
|
|
if (decn_is_nan(&AccDecn)){
|
|
set_dec80_NaN(&AccDecn);
|
|
return;
|
|
}
|
|
//check if negative
|
|
if (AccDecn.exponent < 0){
|
|
negate_decn(&AccDecn);
|
|
need_recip = 1;
|
|
}
|
|
|
|
//check if in range
|
|
copy_decn(&SAVED, &AccDecn); //save = accum
|
|
set_dec80_zero(&BDecn);
|
|
BDecn.lsu[0] = 29;
|
|
BDecn.lsu[1] = 47;
|
|
BDecn.exponent = 2; //b = 294.7
|
|
negate_decn(&BDecn);
|
|
add_decn(); //accum = x - 294.7 (should be negative if in range)
|
|
if (!(AccDecn.exponent < 0)){ //if not negative
|
|
set_dec80_NaN(&AccDecn);
|
|
return;
|
|
}
|
|
copy_decn(&AccDecn, &SAVED); //restore
|
|
|
|
// //normalize
|
|
// remove_leading_zeros(&AccDecn);
|
|
|
|
//initial b = -10*ln(10)
|
|
copy_decn(&BDecn, &DECN_LN_10); //b=ln(10)
|
|
copy_decn(&SAVED, &AccDecn); //save = accum
|
|
copy_decn(&AccDecn, &DECN_1);
|
|
AccDecn.exponent = 1; //accum = 10
|
|
mult_decn(); //accum = 10*ln(10)
|
|
copy_decn(&BDecn, &AccDecn); //b = 10*ln(10)
|
|
negate_decn(&BDecn); //b = -10*ln(10)
|
|
copy_decn(&AccDecn, &SAVED); //restore
|
|
//track number of times 10*ln(10) can be subtracted
|
|
k = 0;
|
|
while (!(AccDecn.exponent < 0)){ //while not negative
|
|
copy_decn(&SAVED, &AccDecn); //save = accum
|
|
//accum -= 10*ln10
|
|
add_decn();
|
|
k++;
|
|
}
|
|
//subtracted 1 time too many
|
|
NUM_TIMES.exponent = (k - 1) * 10; //use exp to store number of times ln(10) subtracted
|
|
copy_decn(&AccDecn, &SAVED); //restore
|
|
#ifdef DEBUG_EXP
|
|
decn_to_str_complete(&AccDecn);
|
|
printf("exp() num_times for 10*ln(10): %s (%d)\n", Buf, NUM_TIMES.exponent);
|
|
#endif
|
|
|
|
//load b with -ln(10)
|
|
copy_decn(&BDecn, &DECN_LN_10); //b = ln(10)
|
|
negate_decn(&BDecn); //b = -ln(10)
|
|
//track number of times ln(10) and then (1 + 10^-j) can be subtracted
|
|
j = UINT8_MAX; //becomes 0 after incrementing to start (1 + 10^-j) series
|
|
do {
|
|
k = 0;
|
|
while (!(AccDecn.exponent < 0)){ //while not negative
|
|
copy_decn(&SAVED, &AccDecn); //save = accum
|
|
//accum -= b (ln10, then ln(1 + 10^-j) series)
|
|
add_decn();
|
|
#ifdef DEBUG_EXP_ALL
|
|
decn_to_str_complete(&AccDecn);
|
|
printf(" %u: %s\n", k, Buf);
|
|
// decn_to_str_complete(&BDecn);
|
|
// printf("(%s)\n", Buf);
|
|
#endif
|
|
k++;
|
|
#ifdef DEBUG_EXP
|
|
assert(k != 255);
|
|
#endif
|
|
}
|
|
//subtracted 1 time too many:
|
|
if (j == UINT8_MAX){
|
|
NUM_TIMES.exponent += k - 1;
|
|
} else {
|
|
NUM_TIMES.lsu[j] = k - 1;
|
|
}
|
|
copy_decn(&AccDecn, &SAVED); //restore
|
|
#ifdef DEBUG_EXP
|
|
decn_to_str_complete(&AccDecn);
|
|
printf("exp() num_times for %d: %s (%d)\n", j, Buf, k-1);
|
|
#endif
|
|
//next j
|
|
j++;
|
|
if (j < NUM_A_ARR){
|
|
//get next ln(1 + 10^-j) for subtraction
|
|
copy_decn(&BDecn, &LN_A_ARR[j]);
|
|
negate_decn(&BDecn);
|
|
} else {
|
|
break;
|
|
}
|
|
} while (1);
|
|
|
|
//build final value
|
|
// (currently accum = save = remainder)
|
|
// calculate 1+remainder
|
|
copy_decn(&BDecn, &DECN_1);
|
|
add_decn();
|
|
//get initial multiplier (10) for ln(10)
|
|
copy_decn(&BDecn, &DECN_1);
|
|
BDecn.exponent = 1; //BDecn = 10
|
|
//do multiplies
|
|
j = UINT8_MAX; //becomes 0 after incrementing to start (1 + 10^-j) series
|
|
do {
|
|
for (k = 0; k < (j==UINT8_MAX ? NUM_TIMES.exponent : NUM_TIMES.lsu[j]); k++){
|
|
mult_decn();
|
|
}
|
|
#ifdef DEBUG_EXP
|
|
decn_to_str_complete(&AccDecn);
|
|
printf("exp() current val for %d: %s\n", j, Buf);
|
|
#endif
|
|
//next j
|
|
j++;
|
|
if (j < NUM_A_ARR){
|
|
//get next multiplier (1 + 10^-j) for ln(1 + 10^-j)
|
|
if (j == 0){
|
|
//set to 2
|
|
BDecn.lsu[0] = 20;
|
|
BDecn.exponent = 0;
|
|
} else if (j == 1) {
|
|
//set to 1.1
|
|
BDecn.lsu[0] = 11;
|
|
//exponent is already 0
|
|
} else {
|
|
//get next (1 + 10^-j)
|
|
shift_right(&BDecn);
|
|
BDecn.lsu[0] = 10;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
} while (1);
|
|
|
|
#ifdef DEBUG_EXP
|
|
decn_to_str_complete(&AccDecn);
|
|
printf("exp() before recip: %s\n", Buf);
|
|
#endif
|
|
//take reciprocal if exp was negative
|
|
if (need_recip){
|
|
recip_decn();
|
|
}
|
|
|
|
#ifdef DEBUG_EXP
|
|
decn_to_str_complete(&AccDecn);
|
|
printf("exp() final val: %s\n", Buf);
|
|
#endif
|
|
|
|
//try not to pollute namespace
|
|
#undef SAVED
|
|
#undef NUM_TIMES
|
|
}
|
|
|
|
void exp10_decn(void){
|
|
//exp10_decn() = exp_decn(AccDecn * ln(10))
|
|
copy_decn(&BDecn, &DECN_LN_10);
|
|
mult_decn();
|
|
exp_decn();
|
|
}
|
|
|
|
void pow_decn(void) {
|
|
if (decn_is_zero(&BDecn)) {
|
|
copy_decn(&AccDecn, &DECN_1);
|
|
return;
|
|
}
|
|
if (decn_is_zero(&AccDecn)) {
|
|
set_dec80_zero(&AccDecn);
|
|
return;
|
|
}
|
|
//calculate AccDecn = AccDecn ^ BDecn
|
|
st_push_decn(&BDecn);
|
|
ln_decn();
|
|
st_pop_decn(&BDecn);
|
|
mult_decn(); //accum = b*ln(accum)
|
|
exp_decn();
|
|
}
|
|
|
|
#ifdef USE_POW_SQRT_IMPL
|
|
void sqrt_decn(void) {
|
|
if (decn_is_zero(&AccDecn)) {
|
|
return;
|
|
}
|
|
if (decn_is_nan(&AccDecn)) {
|
|
return;
|
|
}
|
|
if (AccDecn.exponent < 0){ //negative
|
|
set_dec80_NaN(&AccDecn);
|
|
return;
|
|
}
|
|
st_push_decn(&BDecn); // sqrt should behave like an unary operation
|
|
//b = 0.5
|
|
set_dec80_zero(&BDecn);
|
|
BDecn.lsu[0] = 5;
|
|
pow_decn();
|
|
st_pop_decn(&BDecn);
|
|
}
|
|
#else
|
|
void sqrt_decn(void){
|
|
#define CURR_EST Tmp2Decn //holds current 1/sqrt(x) estimate
|
|
#define X_2 Tmp3Decn //holds copy of original x / 2
|
|
uint8_t i;
|
|
exp_t initial_exp;
|
|
if (decn_is_nan(&AccDecn)) {
|
|
return;
|
|
}
|
|
if (AccDecn.exponent < 0){ //negative
|
|
set_dec80_NaN(&AccDecn);
|
|
return;
|
|
}
|
|
//normalize
|
|
remove_leading_zeros(&AccDecn);
|
|
#ifdef DEBUG_SQRT
|
|
decn_to_str_complete(&AccDecn);
|
|
printf("sqrt in: %s\n", Buf);
|
|
#endif
|
|
//store copy of x
|
|
st_push_decn(&AccDecn);
|
|
//calculate x_orig / 2
|
|
set_dec80_zero(&BDecn);
|
|
BDecn.lsu[0] = 5;
|
|
mult_decn();
|
|
copy_decn(&X_2, &AccDecn);
|
|
//restore x
|
|
st_load_decn(&AccDecn);
|
|
//get initial estimate for 1/sqrt(x) == 10^(-0.5 * log(x)):
|
|
// approximate significand == 10^(-0.5 * log(x_signif))
|
|
// with linear approximation: -0.18 * x_signif + 2.5
|
|
// new exponent part is (10^(-0.5 * log(10^x_exp)))
|
|
// == 10^(-0.5 * x^exp)
|
|
initial_exp = get_exponent(&AccDecn);
|
|
set_exponent(&AccDecn, 0, 0); //clear exponent (Acc is not negative)
|
|
#ifdef DEBUG_SQRT
|
|
printf("sqrt exponent %d ", initial_exp);
|
|
#endif
|
|
if (initial_exp & 0x1){ //odd
|
|
#ifdef DEBUG_SQRT
|
|
printf("(odd) ");
|
|
#endif
|
|
//increment x_exp and
|
|
initial_exp++;
|
|
//approximate estimated significand as (-0.056*x_signif + 0.79) * 10^0.5
|
|
// == -0.18 * x_signif + 2.5
|
|
//b = -0.18
|
|
BDecn.lsu[0] = 18;
|
|
BDecn.exponent = -1; //negative, and exponent = -1
|
|
//a = -0.18 * x_signif
|
|
mult_decn();
|
|
//b = 2.5
|
|
BDecn.lsu[0] = 25;
|
|
BDecn.exponent = 0;
|
|
//a = -0.18 * x_signif + 2.5
|
|
add_decn();
|
|
} else { //even
|
|
//keep x_exp as is and approximate estimated significand as
|
|
// -0.056*x_signif + 0.79
|
|
//b = -0.056
|
|
BDecn.lsu[0] = 56;
|
|
set_exponent(&BDecn, -2, 1);
|
|
//a = -0.056 * x_signif
|
|
mult_decn();
|
|
//b = 0.79
|
|
BDecn.lsu[0] = 7;
|
|
BDecn.lsu[1] = 90;
|
|
BDecn.exponent = 0;
|
|
//a = -0.056*x_signif + 0.79
|
|
add_decn();
|
|
}
|
|
//est_exp = -x_exp / 2;
|
|
initial_exp = -initial_exp / 2;
|
|
//est_exp-- if AccDecn exponent is negative
|
|
// (AccDecn exponent is either 0 or -1, and AccDecn is positive)
|
|
if (AccDecn.exponent != 0){
|
|
initial_exp--;
|
|
}
|
|
set_exponent(&AccDecn, initial_exp, 0); //(initial estimate is never negative)
|
|
copy_decn(&CURR_EST, &AccDecn);
|
|
#ifdef DEBUG_SQRT
|
|
printf(" -> %d\n", initial_exp);
|
|
#endif
|
|
//do newton-raphson iterations
|
|
for (i = 0; i < 6; i++){ //just fix number of iterations for now
|
|
#ifdef DEBUG_SQRT
|
|
decn_to_str_complete(&CURR_EST);
|
|
printf("sqrt %2d: %s\n", i, Buf);
|
|
#endif
|
|
//accum = est * est;
|
|
copy_decn(&BDecn, &AccDecn);
|
|
mult_decn();
|
|
//accum *= x_orig_2; //accum = x/2 * est * est
|
|
copy_decn(&BDecn, &X_2);
|
|
mult_decn();
|
|
//accum = - x/2 * est * est
|
|
negate_decn(&AccDecn);
|
|
//b = 3/2
|
|
set_dec80_zero(&BDecn);
|
|
BDecn.lsu[0] = 15;
|
|
//accum = 3/2 - x/2 * est * est
|
|
add_decn();
|
|
//accum *= est; //accum = 0.5 * est * (3 - x * est * est)
|
|
copy_decn(&BDecn, &CURR_EST);
|
|
mult_decn();
|
|
//est = accum;
|
|
copy_decn(&CURR_EST, &AccDecn);
|
|
}
|
|
|
|
//calc sqrt from recip_sqrt
|
|
st_pop_decn(&BDecn);
|
|
mult_decn();
|
|
|
|
#undef CURR_EST
|
|
#undef X_COPY
|
|
}
|
|
#endif //USE_POW_SQRT_IMPL
|
|
|
|
|
|
// see W.E. Egbert, "Personal Calculator Algorithms II: Trigonometric functions"
|
|
void project_decn_into_0_2pi(void) {
|
|
const uint8_t is_negative = (AccDecn.exponent < 0);
|
|
exp_t exponent;
|
|
|
|
remove_leading_zeros(&AccDecn);
|
|
if (is_negative) {
|
|
negate_decn(&AccDecn);
|
|
}
|
|
exponent = get_exponent(&AccDecn);
|
|
copy_decn(&BDecn, &DECN_2PI);
|
|
if (compare_magn() > 0) {
|
|
do {
|
|
do {
|
|
copy_decn(&BDecn, &DECN_2PI);
|
|
BDecn.exponent = exponent;
|
|
if (compare_magn() >= 0) {
|
|
negate_decn(&BDecn);
|
|
add_decn();
|
|
} else {
|
|
break;
|
|
}
|
|
} while (1);
|
|
exponent--;
|
|
} while (exponent >= 0);
|
|
}
|
|
|
|
if (is_negative) {
|
|
negate_decn(&AccDecn);
|
|
copy_decn(&BDecn, &DECN_2PI);
|
|
add_decn();
|
|
}
|
|
}
|
|
|
|
// K. Shirriff, "Reversing Sinclair's amazing 1974 calculator hack - half the ROM of the HP-35"
|
|
// http://files.righto.com/calculator/sinclair_scientific_simulator.html
|
|
#define SIN Tmp2Decn
|
|
#define COS Tmp3Decn
|
|
#define THETA Tmp4Decn
|
|
void sincos_decn(const uint8_t sincos_arctan) {
|
|
const uint8_t is_negative = AccDecn.exponent < 0;
|
|
if (sincos_arctan) {
|
|
set_dec80_zero(&THETA);
|
|
if (is_negative) negate_decn(&AccDecn);
|
|
copy_decn(&COS, &AccDecn);
|
|
copy_decn(&SIN, &DECN_1);
|
|
} else {
|
|
project_decn_into_0_2pi();
|
|
copy_decn(&THETA, &AccDecn);
|
|
copy_decn(&COS, &DECN_1);
|
|
set_dec80_zero(&SIN);
|
|
// 0.0 00 5
|
|
SIN.lsu[2] = 50;
|
|
negate_decn(&SIN);
|
|
}
|
|
do {
|
|
if (sincos_arctan) {
|
|
// THETA is in AccDecn from previous iteration
|
|
if (COS.exponent < 0) {
|
|
if (is_negative) negate_decn(&AccDecn);
|
|
break;
|
|
}
|
|
} else {
|
|
if (THETA.exponent < 0) {
|
|
break;
|
|
}
|
|
}
|
|
// COS = COS - SIN / 1000
|
|
copy_decn(&AccDecn, &COS);
|
|
copy_decn(&BDecn, &SIN);
|
|
shift_right(&BDecn);
|
|
shift_right(&BDecn);
|
|
shift_right(&BDecn);
|
|
negate_decn(&BDecn);
|
|
add_decn();
|
|
copy_decn(&COS, &AccDecn);
|
|
// SIN = SIN + COS / 1000
|
|
copy_decn(&AccDecn, &SIN);
|
|
copy_decn(&BDecn, &COS);
|
|
shift_right(&BDecn);
|
|
shift_right(&BDecn);
|
|
shift_right(&BDecn);
|
|
add_decn();
|
|
copy_decn(&SIN, &AccDecn);
|
|
// THETA = THETA -/+ 0.0 01
|
|
copy_decn(&AccDecn, &THETA);
|
|
set_dec80_zero(&BDecn);
|
|
BDecn.lsu[1] = 1;
|
|
if (!sincos_arctan) negate_decn(&BDecn);
|
|
add_decn();
|
|
copy_decn(&THETA, &AccDecn);
|
|
} while (1);
|
|
}
|
|
|
|
void sin_decn(void) {
|
|
sincos_decn(0);
|
|
copy_decn(&AccDecn, &SIN);
|
|
}
|
|
|
|
void cos_decn(void) {
|
|
sincos_decn(0);
|
|
copy_decn(&AccDecn, &COS);
|
|
}
|
|
|
|
void tan_decn(void) {
|
|
sincos_decn(0);
|
|
copy_decn(&AccDecn, &SIN);
|
|
copy_decn(&BDecn, &COS);
|
|
div_decn();
|
|
}
|
|
|
|
void arctan_decn(void) {
|
|
sincos_decn(1);
|
|
}
|
|
|
|
// see W.E. Egbert, "Personal Calculator Algorithms III: Inverse Trigonometric Functions"
|
|
void arcsin_decn(void) {
|
|
st_push_decn(&AccDecn);
|
|
copy_decn(&BDecn, &AccDecn);
|
|
mult_decn();
|
|
negate_decn(&AccDecn);
|
|
copy_decn(&BDecn, &DECN_1);
|
|
add_decn();
|
|
sqrt_decn();
|
|
recip_decn();
|
|
st_pop_decn(&BDecn);
|
|
mult_decn();
|
|
|
|
sincos_decn(1);
|
|
}
|
|
|
|
void arccos_decn(void) {
|
|
arcsin_decn();
|
|
negate_decn(&AccDecn);
|
|
copy_decn(&BDecn, &DECN_PI2);
|
|
add_decn();
|
|
}
|
|
#undef SIN
|
|
#undef COS
|
|
#undef THETA
|
|
|
|
void to_degree_decn(void) {
|
|
copy_decn(&BDecn, &DECN_1RAD);
|
|
mult_decn();
|
|
}
|
|
|
|
void to_radian_decn(void) {
|
|
copy_decn(&BDecn, &DECN_1RAD);
|
|
div_decn();
|
|
}
|
|
|
|
void pi_decn(void) {
|
|
set_dec80_zero(&BDecn);
|
|
BDecn.lsu[0] = 5; // 0.5 00 ..
|
|
copy_decn(&AccDecn, &DECN_2PI);
|
|
mult_decn();
|
|
}
|
|
|
|
static void set_str_error(void){
|
|
Buf[0] = 'E';
|
|
Buf[1] = 'r';
|
|
Buf[2] = 'r';
|
|
Buf[3] = 'o';
|
|
Buf[4] = 'r';
|
|
Buf[5] = '\0';
|
|
}
|
|
|
|
#ifdef DESKTOP
|
|
int
|
|
#else
|
|
int8_t
|
|
#endif
|
|
decn_to_str(const dec80* x){
|
|
#define INSERT_DOT() Buf[i++]='.'
|
|
uint8_t i = 0;
|
|
uint8_t digit100;
|
|
exp_t exponent = 0;
|
|
uint8_t trailing_zeros = 0;
|
|
uint8_t use_sci = 0;
|
|
static __xdata dec80 tmp;
|
|
|
|
//handle corner case of NaN
|
|
if (decn_is_nan(x)){
|
|
set_str_error();
|
|
#ifdef DEBUG
|
|
printf (" corner case NaN ");
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
//copy and normalize
|
|
copy_decn(&tmp, x);
|
|
remove_leading_zeros(&tmp);
|
|
//handle corner case of 0
|
|
if (tmp.lsu[0] == 0){
|
|
#ifdef DEBUG
|
|
printf (" corner case, set to 0 ");
|
|
#endif
|
|
Buf[0] = '0';
|
|
Buf[1] = '\0';
|
|
return 0;
|
|
}
|
|
//check sign of number
|
|
if (tmp.exponent < 0){
|
|
#ifdef DEBUG
|
|
printf (" negative ");
|
|
#endif
|
|
Buf[i] = '-';
|
|
i++;
|
|
}
|
|
//check if we should use scientific notation
|
|
exponent = get_exponent(&tmp);
|
|
if (exponent > (num_digits_display - 1) || exponent < -3){
|
|
use_sci = 1;
|
|
}
|
|
//pad zeros right of decimal point if needed
|
|
if (!use_sci && exponent < 0){
|
|
exp_t j;
|
|
Buf[i] = '0';
|
|
i++;
|
|
INSERT_DOT();
|
|
//pad zeros right of decimal point
|
|
// for (j = exponent + 1; j < 0; j++){ <--- results in undefined behavior (signed overflow), and causes crash
|
|
for (j = -exponent -1; j > 0; --j){
|
|
Buf[i] = '0';
|
|
i++;
|
|
}
|
|
}
|
|
//print 1st digit
|
|
Buf[i] = (tmp.lsu[0] / 10) + '0';
|
|
i++;
|
|
if (use_sci) {
|
|
INSERT_DOT();
|
|
} else {
|
|
if (exponent == 0){
|
|
INSERT_DOT();
|
|
}
|
|
exponent--;
|
|
}
|
|
//print 2nd digit
|
|
Buf[i] = (tmp.lsu[0] % 10) + '0';
|
|
if (tmp.lsu[0] % 10 == 0 && (use_sci || exponent < 0)){
|
|
|
|
trailing_zeros = 1;
|
|
}
|
|
i++;
|
|
if (!use_sci){
|
|
if (exponent == 0){
|
|
INSERT_DOT();
|
|
}
|
|
exponent--;
|
|
}
|
|
//print rest of significand
|
|
for (digit100 = 1 ; digit100 < num_digits_display/2; digit100++){
|
|
//print 1st digit
|
|
Buf[i] = (tmp.lsu[digit100] / 10) + '0';
|
|
i++;
|
|
if (!use_sci){
|
|
if (exponent == 0){
|
|
INSERT_DOT();
|
|
}
|
|
exponent--;
|
|
}
|
|
//print 2nd digit
|
|
Buf[i] = (tmp.lsu[digit100] % 10) + '0';
|
|
i++;
|
|
if (!use_sci){
|
|
if (exponent == 0){
|
|
INSERT_DOT();
|
|
}
|
|
exponent--;
|
|
}
|
|
//track trailing 0s
|
|
if (tmp.lsu[digit100] == 0 && (use_sci || exponent < 0)){
|
|
if (use_sci || exponent < -2){ //xx.00
|
|
trailing_zeros += 2;
|
|
} else if (exponent == -2){ //xx.0
|
|
trailing_zeros += 1;
|
|
}
|
|
} else if (tmp.lsu[digit100]%10 == 0 && (use_sci || exponent < 0)){
|
|
trailing_zeros = 1;
|
|
} else {
|
|
trailing_zeros = 0;
|
|
}
|
|
}
|
|
|
|
//remove trailing zeros
|
|
if (use_sci || exponent <= 0){
|
|
i -= trailing_zeros;
|
|
}
|
|
Buf[i] = '\0';
|
|
|
|
//calculate exponent
|
|
exponent = get_exponent(&tmp); //base 100
|
|
#ifdef DEBUG
|
|
printf (" exponent (%d)", exponent);
|
|
#endif
|
|
//print exponent
|
|
if (use_sci){
|
|
//check for overflow
|
|
#ifdef DESKTOP
|
|
if (exponent > DEC80_MAX_EXP || exponent < DEC80_MIN_EXP){
|
|
#else
|
|
if (exponent > DECN_MAX_PRINT_EXP || exponent < DECN_MIN_PRINT_EXP){
|
|
#endif
|
|
set_str_error();
|
|
return 0;
|
|
}
|
|
return exponent;
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
printf (" final i (%d) ", i);
|
|
for (int jjj = 0; jjj < DEC80_NUM_LSU; jjj++){
|
|
printf(" %02d", tmp.lsu[jjj]);
|
|
}
|
|
printf("\n");
|
|
#endif
|
|
}
|
|
|
|
#ifdef DESKTOP
|
|
//complete string including exponent
|
|
void decn_to_str_complete(const dec80* x){
|
|
int exponent = decn_to_str(x);
|
|
int i;
|
|
//find end of string
|
|
for (i = 0; Buf[i] != '\0'; i++);
|
|
//add exponent
|
|
if (exponent != 0){
|
|
Buf[i++] = 'E';
|
|
if (exponent < 0){
|
|
Buf[i++] = '-';
|
|
u32str(-exponent, &Buf[i], 10); //adds null terminator automatically
|
|
} else {
|
|
u32str(exponent, &Buf[i], 10); //adds null terminator automatically
|
|
}
|
|
}
|
|
}
|
|
|
|
void build_decn_at(dec80* dest, const char* signif_str, exp_t exponent){
|
|
dec80 tmp;
|
|
copy_decn(&tmp, &AccDecn); //save
|
|
build_dec80(signif_str, exponent);
|
|
copy_decn(dest, &AccDecn);
|
|
copy_decn(&AccDecn, &tmp); //restore
|
|
}
|
|
|
|
#endif //DESKTOP
|
|
|