-2

I am attempting to create a device that talks to a Casio fx-9750 calculator through its serial port with an Arduino. I have figured out how to receive values and decode the BCD, but I'm stuck on how to create the required values from a float (to transmit back).

The calculator sends a data packet, which has an exponent value, several data values, and a byte that contains information about negativity, imaginary parts, etc. Each data value is worth one hundredth of the previous one, so the first is the amount of 10s, the next the amount of 0.1s, the next the amount of 0.001s, etc. This continues on until the 0.0000000000001s, though this is out of the range of what I'll really need, so that level of accuracy is not really important to me. The output of my receiving program looks like this:

Exponent: 1
10s: 1
0.1s: 23
0.001s: 40

This represents 12.34.
The general equation I worked out was: (let a=10s, b=0.1s, e=exponent etc)

((a*10)+(b*0.1)+(c*0.001))*10^(E-1)

If the exponent were to change to two:

Exponent: 2
10s: 1
0.1s: 23
0.001s: 40

This would represent 123.4

This method of dropping by hundredths each time is presumably used because they can store two digits in each byte with BCD, so it is most efficient to let each row have two digits as each row is stored as one byte.

I have come up with an equation that can calculate the exponent by counting the amount of digits before the decimal point and subtracting two, however this seems messy as it involves strings. I think a purely mathematical solution would be more elegant, if it is possible.

What is the fastest and simplest way to go from a normal number (e.g. 123.4) into this arrangement? A solution in Arduino language would be greatly appreciated, but any insight whatsoever into the mathematical process needed would be equally valued.

Edit regarding floats: I should clarify - I will be dealing with floats in other parts of my program and would like my inputted values to be compatible with numbers of any size (within reason, as stated before). I have no problem with multiplying them to be ints or casting them as other datatypes.

Matthew B
  • 1
  • 1
  • 4
  • What does BCD have to do with floating point? Why do you need floating point? _If_ you need floating point, Arduino is a horrible choice, given that AVR is a legacy 8 bit architecture with no FPU. – Lundin Aug 20 '18 at 09:29
  • `how do I go from a normal number (123.4) into this arrangement.` divide by ten (or hundred) until it is between 1 and 10. (or 1 and 100) Count the number of divisions. – joop Aug 20 '18 at 09:31

1 Answers1

0

Hah, that was fun!

#include <stdio.h>
#include <assert.h>
#include <math.h>
#include <float.h>

struct num_s {
  // exponent
  int e;

  // v[0] is *10
  // v[1] is *0.01
  // v[2] is *0.0001
  // and so on...
  // to increase precision increase array count
  int v[6];
};

#define NUM_VALSCNT (sizeof(((struct num_s*)0)->v)/sizeof(((struct num_s*)0)->v[0]))

// creates num_s object from a double
struct num_s num_create(double v) {
  struct num_s t;

  // find exponent so that v <= 10
  t.e = 0;
  while (fabs(v) >= 10.0) {
    ++t.e;
    v /= 10.0;
  }

  // for each output number get the integral part of number
  // then multiply the rest by 100 and continue
  for (size_t i = 0; i < sizeof(t.v) / sizeof(t.v[0]); ++i) {
    const double tmp = fmod(v, 1.0);
    t.v[i] = v - tmp;
    v = tmp * 100;
  }
  return t;
}

// converts back from num object to double
double num_get(struct num_s t) {
  double denom = 10;
  double ret = 0;
  for (size_t i = 0; i < sizeof(t.v) / sizeof(t.v[0]); ++i) {
    ret += denom * t.v[i];
    denom /= 100;
  }
  return ret * pow(10, t.e - 1);
}

void num_println(struct num_s t) {
  printf("%f =", num_get(t));
  for (size_t i = 0; i < sizeof(t.v) / sizeof(t.v[0]); ++i) {
    printf(" %d", t.v[i]);
  }
  printf(" %d\n", t.e);
}

// returns the precision of numbers
// the smallest number we can represent in num object
double num_precision(void) {
  return pow(0.1, (NUM_VALSCNT - 1) * 2) * 10;
}

int num_unittests(void) {
  const double tests[][3] = {
    { 123.49, 123.5, 123.51, }
  };
  for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); ++i) {
    const double tmp = num_get(num_create(tests[i][1]));
    if (!(tests[i][0] <= tmp && tmp <= tests[i][2])) {
      return i + 1;
    }
  }
  return 0;
}

int main() {
  num_println(num_create(12.3456789));
  num_println(num_create(123.5));
  num_println(num_create(12.35));
  printf("%d\n", num_unittests());
  return 0;
}
dda
  • 6,030
  • 2
  • 25
  • 34
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • Executing this code on Arduino will however not be much fun at all. This will be incredibly slow and kill off considerable amounts of flash. Turns out Arduino is not a PC. – Lundin Aug 20 '18 at 10:57
  • Probably because floating point are big for 8-bits. You can try checking how big the functions are, what takes the most space. You can move from double to float, change `fabs(v) >= 10.0` to first check if v is negative. You can change `fmod` to just `(int)v`. Or you can try to just move to only integers with some pre-set arbitrary chosen precision. Passing pointers instead of values can remove some flash usage. – KamilCuk Aug 20 '18 at 11:13
  • No, the main issue is that Arduino is not a PC, so it does not have a FPU. And it is not clear to me why floating point is needed in the first place. – Lundin Aug 20 '18 at 11:24
  • That being said, passing structs by value is bad practice on all computers, even on PC. – Lundin Aug 20 '18 at 11:24
  • Thanks, code works great. Regarding speeds, I ran a test by using the arduino millis() function. It repeatedly took 25ms to process the number 1233.3456789. With an enormous number (1233123123134.3456789) it took 26ms. I agree this does seem somewhat slow, but I very little experience with this kind of thing. Is this slow? and how could it be improved? – Matthew B Aug 21 '18 at 08:05