Base conversions: issue with fractions - java

I have written a system that is able to convert any base (2-36) to another base with whole numbers, and it can convert any real number from base 10 to any other base (2-36).
My problem arises with converting a rational/irrational number from any base besides 10 to another base.
I use the following algorithm for right-side of the decimal point conversion:
1) Take the right side of the decimal point (0.xxxxxx--->) in the input and multiply it by the base you are converting to.
2) Take the number greater than one (left of the point) and add it to the right side of the converted number.
3) Take the right side of the product and use it in the next repetition as the multiplier (it times the base)
4) Repeat until satisfied or left with a whole number (0 on the right side).
This works nicely for converting any floating point number from decimal to another base, but obviously you can't convert FROM a base that isn't decimal.
So what I tried is converting that initial value to the right of the decimal to base 10, performing the math part, and then converting it back to the original base for when I add it to the output value (it's converted to the new base before being added).
Unfortunately, this returns incorrect results for the right side of the decimal point. So, I have answers that are always correct on the left side, but incorrect on the right if converting from a base that is not base 10.
Does anyone have any ideas for how to make this work? Or perhaps it just won't?
EDIT
Alternatively, can anyone link me/show me how to convert a rational hexadecimal value into decimal? That alone would be sufficient for me to work around this issue.
SOLUTION
I found a fairly easy workaround to this problem for anyone else in the future who reads this question.
All you have to do is the take the number on the right side of the decimal (whatever base it may be) and convert it to decimal (you can see how to convert integers here). Then take that number and divide it by the greatest place value in it. For instance:
A.C
C == 12 (dec)
12 / 16 = .75 (this is the fractional value in decimal)
You can then take that fractional decimal value and run it through the algorithm I discussed above.
Thanks for everyone's help on this issue!

Using floating point implies that you do not want to perform accurate computation.
Only numbers written in bases 2, 4, 8, 16,... can ever be accurately represented in Java floating point values (leaving integers aside). This is due to the limitations of the floating point representation.
Only numbers written in bases 2, 4, 5, 8, 10, 16, 20, 25, 32,... can be accurately printed in the decimal base. This is due to the limitation of our decimal number system.
I expect that you should therefore adapt some rules as to rounding of results and implement those throughout the algorithm. Make sure that you round rather than truncate, otherwise going through the floating points will give you incorrect results even in cases where the precision of the double type is sufficient for your purposes, or where the number can be accurately represented.
If you want to perform the computation in much higher precision, look at the BigInteger class and redesign your algorithm exclusively in integers. Alternatively, use a library for working with fractions; this is useful because the inputs to your algorithm can always be accurately represented as a fraction. However, in the end it always boils down to defining result rounding rules and implementing them correctly.
Edit:
As I learned from the comments, you prefer to emit output digits gradually, before the whole input is read. This is basically possible, but
You need to keep an interval, rather than a single number, as the "accummulator"; for example, if you have so far read 0.1111 in ternary, then you know that the output lies between 0.49382716 and 0.50617284 and you cannot emit even the first decimal digit after the decimal point at this stage. This is necessary to avoid seeing outputs like 0.4999999992 on the most "rational" of inputs.
When the full input is read, it is safer to "round up" and emit output based on the upper bound of the interval rather than on the bottom bound. This way 0.1111 in ternary will be converted to 0.5 in decimal. (This can be ignored if you are limited to hex to decimal conversion.)
Keep track of the maximum precision achieved by the input (logarithm of the width of the interval) and make sure you emit no more output digits than the input guarantees.
Use an internal representation of interval endpoints (lower and upper bounds) that can safely deal with the maximum precision you need.
Keep in mind that even quite popular software occasionally gets the details of this algorithm wrong and stay away from representing any intermediate results in floating point data types, or truncate the input to a number of digits that they can safely represent if it is longer.
You mention irrational numbers in the question, but every number that can be expressed with a finite (or periodically repeating) expansion, regardless of the base used, is necessary a rational number.
In conversions from hex to decimal, the output can even always be represented accurately which allows some simplifications like indefinitely waiting for the lower and upper bound to converge.

Related

How can I send with two bytes a float from type %8.5f over bluetooth low energy to my android app

I try to send sensor data continuously from a stm32wb55 to my own android app.
I receive two bytes from a acceleration sensor and convert those correctly on my stm32wb55 to a float with format (XX.XXXXX, float can be negative).
Now I want to send exactly this float to my own android app.
Before, I have send two bytes from type "int or uint" to my android app and tried to convert those the same way I have done already on the stm32wb55. But the values on my screen are up to 50% of cases false. So now I try to send the float value directly, so that no more conversion on my phone is needed.
EDIT: After your contributions, I have forget my poor idea to send a float to my android app. I tried again to send the two byte integers and convert those the right way on my app. Now it works how it should. I have found the solution I needed on this post:2 Chars to Short in C.
By combining the two bytes to a 16-Bit Integer, I just needed 0x00ff & for my LSB like it be used in the answer of the referenced post.
to a float with format (XX.XXXXX, float can be negative).
This is impossible.
floats are a 32-bit IEEE754 floating point number. They do not work like you (apparently) think they do.
Specifically, they are binary constructs with a mantissa system. I'll try to explain why it doesn't work like you think they do: What is 1/3, in decimal? You'll find that you can't write it in decimal no matter how many 'bits' (digits) you use. You'll never quite get it. There's always another 3 to add.
floating point works the same way, but it's in binary (base 2) and not decimal (base 10). That means there are numbers that work great in decimal (such as 1/10th which in decimal is 0.1, with perfect accuracy) but which are like 1/3 in binary: No matter how many bits you use, you'll never get it.
Here's another way to think about it: 32-bit, so, there are only 2^32 (about 4 billion) different values. In other words, of all the numbers in existence (and there is an infinite infinity of them: There are infinite numbers, and within any 2 consecutive numbers, another infinity of numbers), only at most 4 billion are blessed: 4 billion of all numbers in existence are representable by a float value. If you try to represent a number that isn't blessed with a float, then java / your CPU will just round it to the nearest blessed number and gives you no real opportunity to deal with the error (after all, how would you represent the error? It is rather unlikely to be blessed, either).
Thus, say, '12.34567'? That's not a blessed number - therefore, your float cannot possibly represent that. It'll instead be a number very close to it, and probably a number that would round to 12.34567 if you round it to 5 digits.
send exactly this float to my own android app.
So, no, you don't want to do that. You want to send 12.34567 to your android app, not the 32 bits that represent it. Unless you intend for the android side of the app to do the rounding, which you probably should. Note that I bet there are numbers that fit the 'XX.YYYYY' pattern that just do not 'work' as a float (they round such that you're off by 1). If that's a problem, don't use floats (use doubles where I doubt that you'll find an XX.YYYYY that doesn't have a blessed number such that it rounds correctly due to having more bits to work with, or use a string, or use 2 ints, or use a single int, and have an agreement that both sides know that the int 1234567 represents 12.34567).
That last one sounds like the most convenient trick for you here, but it's hard to tell as you haven't provided much detail.
Something like (but note that the float may be off by 1 or so!):
sender side:
double v = theFloat; // doubles have less error
int z = (int) (v * 100000);
sendToPhone(z);
receiver side:
int z = getFromDevice();
double v = z;
v /= 100000;
float theFloat = (float) v;
The above will end up automatically rounding off (rounding down for positive numbers and up for negative numbers) any digits after the floating point beyond the 5 you want), and can deal with numbers up to plus or minus 21473.99999. Sounds like that'll easily cover your needs.
NB: You'll have to write the 'multiply by 100000 and then convert to an int32' code for your stm32wb55, the above is how you'd write it if the stm32wb55 was programmed in java, which I would assume it isn't. The 'go to double before multiplying by 100000 is a probably irrelevant optimization, I wouldn't be too worried if you can't do that. Note that CPUs are not guaranteed to use the exact same IEEE754 representation for floats/doubles that java does, which is why you should definitely not attempt to send the value as a float/double across the bluetooth channel, but as something universally agreed upon, such as 'a 2's complement 32-bit integer value'.

How to round a double/float to BINARY precision?

I am writing tests for code performing calculations on floating point numbers. Quite expectedly, the results are rarely exact and I would like to set a tolerance between the calculated and expected result. I have verified that in practice, with double precision, the results are always correct after rounding of last two significant decimals, but usually after rounding the last decimal. I am aware of the format in which doubles and floats are stored, as well as the two main methods of rounding (precise via BigDecimal and faster via multiplication, math.round and division). As the mantissa is stored in binary however, is there a way to perform rounding using base 2 rather than 10?
Just clearing the last 3 bits almost always yields equal results, but if I could push it and instead 'add 2' to the mantissa if its second least significast bit is set, I could probably reach the limit of accuracy. This would be easy enough, expect I have no idea how to handle overflow (when all bits 52-1 are set).
A Java solution would be preferred, but I could probably port one for another language if I understood it.
EDIT:
As part of the problem was that my code was generic with regards to arithmetic (relying on scala.Numeric type class), what I did was an incorporation of rounding suggested in the answer into a new numeric type, which carried the calculated number (floating point in this case) and rounding error, essentially representing a range instead of a point. I then overrode equals so that two numbers are equal if their error ranges overlap (and they share arithmetic, i.e. the number type).
Yes, rounding off binary digits makes more sense than going through BigDecimal and can be implemented very efficiently if you are not worried about being within a small factor of Double.MAX_VALUE.
You can round a floating-point double value x with the following sequence in Java (untested):
double t = 9 * x; // beware: this overflows if x is too close to Double.MAX_VALUE
double y = x - t + t;
After this sequence, y should contain the rounded value. Adjust the distance between the two set bits in the constant 9 in order to adjust the number of bits that are rounded off. The value 3 rounds off one bit. The value 5 rounds off two bits. The value 17 rounds off four bits, and so on.
This sequence of instruction is attributed to Veltkamp and is typically used in “Dekker multiplication”. This page has some references.

The accuracy of a double in general programming and Java

I understand that due to the nature of a float/double one should not use them for precision important calculations. However, i'm a little confused on their limitations due to mixed answers on similar questions, whether or not floats and doubles will always be inaccurate regardless of significant digits or are only inaccurate up to the 16th digit.
I've ran a few examples in Java,
System.out.println(Double.parseDouble("999999.9999999999");
// this outputs correctly w/ 16 digits
System.out.println(Double.parseDouble("9.99999999999999");
// This also outputs correctly w/ 15 digits
System.out.println(Double.parseDouble("9.999999999999999");
// But this doesn't output correctly w/ 16 digits. Outputs 9.999999999999998
I can't find the link to another answer that stated that values like 1.98 and 2.02 would round down to 2.0 and therefore create inaccuracies but testing shows that the values are printed correctly. So my first question is whether or not floating/double values will always be inaccurate or is there a lower limit where you can be assured of precision.
My second question is in regards to using BigDecimal. I know that I should be using BigDecimal for precision important calculations. Therefore I should be using BigDecimal's methods for arithmetic and comparing. However, BigDecimal also includes a doubleValue() method which will convert the BigDecimal to a double. Would it be safe for me to do a comparison between double values that I know for sure have less than 16 digits? There will be no arithmetic done on them at all so the inherent values should not have changed.
For example, is it safe for me to do the following?
BigDecimal myDecimal = new BigDecimal("123.456");
BigDecimal myDecimal2 = new BigDecimal("234.567");
if (myDecimal.doubleValue() < myDecimal2.doubleValue()) System.out.println("myDecimal is smaller than myDecimal2");
Edit: After reading some of the responses to my own answer i've realized my understanding was incorrect and have deleted it. Here are some snippets from it that might help in the future.
"A double cannot hold 0.1 precisely. The closest representable value to 0.1 is 0.1000000000000000055511151231257827021181583404541015625. Java Double.toString only prints enough digits to uniquely identify the double, not the exact value." - Patricia Shanahan
Sources:
https://stackoverflow.com/a/5749978 - States that a double can hold up to 15 digits
I suggest you read this page:
https://en.wikipedia.org/wiki/Double-precision_floating-point_format
Once you've read and understood it, and perhaps converted several examples to their binary representations in the 64 bit floating point format, then you'll have a much better idea of what significant digits a Double can hold.
As a side note, (perhaps trivial) a nice and reliable way to store a known precision of value is to simply multiply it by the relevant factor and store as some integral type, which are completely precise.
For example:
double costInPounds = <something>; //e.g. 3.587
int costInPence = (int)(costInPounds * 100 + 0.5); //359
Plainly some precision can be lost, but if a required/desired precision is known, this can save a lot of bother with floating point values, and once this has been done, no precision can be lost by further manipulations.
The + 0.5 is to ensure that rounding works as expected. (int) takes the 'floor' of the provided double value, so adding 0.5 makes it round up and down as expected.

Weird Java behavior: How come adding doubles with EXACTLY two decimal places result to a double with MORE THAN two decimal places?

If I have an array of doubles that each have EXACTLY two decimal places, add them up altogether via a loop, and print out the total, what comes out is a number with MORE THAN two decimal places. Which is weird, because theoretically, adding two numbers that each have 2 and only 2 decimal places will NEVER produce a number that has a non-zero digit beyond the hundredths place.
Try executing this code:
double[] d = new double[2000];
for (int i = 0; i < d.length; i++) {
d[i] = 9.99;
}
double total = 0,00;
for (int i = 0; i < d.length; i++) {
total += d[i];
if (("" + total).matches("[0-9]+\\.[0-9]{3,}")) { // if there are 3 or more decimal places in the total
System.out.println("total: " + total + ", " + i); // print the total and the iteration when it occured
}
}
In my computer, this prints out:
total: 59.940000000000005, 5
If I round off the total to two decimal places then I'd get the same number as I would if I manually added 9.99 six times on a calculator. But how come this is happening and where are the extra decimal places coming from? Am I doing something wrong or (I doubt this is likely) is this a Java bug?
Are you familiar with base 10 to base 2 conversion (decimal to binary) for fractions? If not, look it up.
Then you'll see that although 9.99 looks pretty normal in base 10, it doesn't really look that nice in binary; It looks like a repeating decimal, but in binary. I'm sure you've seen a repeating decimal before, right? It doesn't end. But Java (or any language for that matter) has to save that infinite sequence of digits into a limited number of bytes. And that's when the extra digits appear. When you convert that truncated binary back to decimal, you're really dealing with a different number. The number stored in the variable isn't 9.99 exactly, it something like 9.9999999991 (just an example, I didn't work out the math).
But you're probably interested on how to solve this, right? Look up the BigDecimal class. That's what you want to use for your calculations, especially when dealing with currency. Also, look up DecimalFormat, which is a class for writing a number as a properly formatted string. I think it does rounding for you when you want to show only 2 decimal digits and your number has a lot more, for example.
If I have an array of doubles that each have EXACTLY two decimal places
Let's stop right there, because I suspect you don't. For example, you give 9.99 in your sample code. That isn't really 9.99. That's "the closest double to 9.99" as 9.99 itself can't be exactly represented in binary floating point.
At that point, the rest of your reasoning goes out of the window.
If you want values with an exact number of decimal digits, you should use a type which stores values in a decimal-centric manner, such as BigDecimal. Alternatively, store everything as integers and "know" that you're actually remembering "the value * 100" instead.
Doubles are represented in a binary format on the computer (). This means that certain numbers cannot be represented accurately, so the computer will use the closest number that can be represented.
E.g. 10.5 = 2^3+2+2^(-1) = 1.0101 * 2^3 (here the mantissa is in binary)
but 10.1 = 2^3+2+2^(-4)+2^(-5)+(infinite series here) = 1.0100001... * 2^3
9.99 is such a number with infinite representation. Thus when you add them together, the finite representation used by the computer is used in the calculation and the result will be even more further away from the mathematical sum than the originals were from their true representation. This is why you see more digits displayed than used in the original numbers.
this is because of floating point arithmetics.
doubles and floats are not exactly real numbers, there are finite number of bits to represent them while there are infinite number of real numbers [in any range], so you cannot represent all real numbers - You are getting the closest number you can have with the floating point representation.
Whenever you deal with floating points - remember that they are only an approximation to the number you are seeking. You might want to use BigDecimal if you want the exact number [or at least control the error].
More info can be found at this article
Use BigDecimal to perform floating point calculations with precision. It's a must when it comes to money.
This is a known issue that stems in the fact that binary calculations don't allow for precise floating point operations. Look at "floating point arithmetics" for more details.
This is due to inaccuracies when it comes to representing decimal numbers using a binary floating point value. In other words, the double literal 0.99 does not actually represent the mathematical value 9.99.
To reveal exactly what number a value, such as 9.99 represents you could let BigDecimal print the value.
Code to reveal the exact value:
System.out.println(new BigDecimal(9.99));
Output:
9.9900000000000002131628207280300557613372802734375
Btw, your reasoning would be completely accurate if you were taking about binary places instead of decimal places, since a number with two binary places can be exactly represented by a binary floating point value.

Is there a rounding algorithm that undoes base 2 conversion and infers precision? *hopefully in java*

If I have a number like 3.01 the computer seems to think the best double is the 64 bit number:
3.0099999999999997868371792719699442386627197265625
Is there some way better than looking for say more than four 9's or 0's that I can generically "round" to the precise base 10 representation?
Is there an algo that would take that 3.00999999... mess and return 3.01 WITHOUT me specifying that I want that precision.
I think most of the numbers I'm dealing with should be small enough that 64-bits will not have ambiguities.
No - because presumably you might have actually specified 3.0099999999999997868 as the input number, and wouldn't want that same value to be rounded to 3.01. Basically, you've lost information when converting from a decimal value to binary floating point - you can't get that information back.
If you're interested in decimal values rather than just the magnitude, you should consider using BigDecimal instead of double. (What do these values represent?)
EDIT: As noted by other answers, Java will give you 3.01 anyway when you just use toString, however you came to the original value. This is specified in Double.toString:
How many digits must be printed for the fractional part of m or a? There must be at least one digit to represent the fractional part, and beyond that as many, but only as many, more digits as are needed to uniquely distinguish the argument value from adjacent values of type double. That is, suppose that x is the exact mathematical value represented by the decimal representation produced by this method for a finite nonzero argument d. Then d must be the double value nearest to x; or if two double values are equally close to x, then d must be one of them and the least significant bit of the significand of d must be 0.
If that's good enough for you, it'll make life easier... but it sounds like you should be thinking about it more fundamentally.
If you want 10 digits of precision, you need to round to that precision. Even if you use BigDecimal you can avoid representation error, but sooner or later you will have to know how to deal with precision.
double d = 3.01;
System.out.println(d); // rounds the answer slightly
prints
3.01
There are many workarounds for representation and rounding error, however often the built in tools will deal with it for you.
It's clear that you cannot expect to always get the original number back since there are many numbers that map to the same float. For example, you cannot distinguish between these numbers:
3.0099999999999997868371792719699442386627197265625
3.009999999999999786837179271969944238662
3.009999999999999786837179271
3.0099999999999997
3.01
However, Python has an interesting take on this: if you give it the number 3.0099999999999997868371792719699442386627197265625, it will reply with 3.01:
Python 2.7.2+ (default, Nov 30 2011, 19:22:03)
[GCC 4.6.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 3.0099999999999997868371792719699442386627197265625
3.01
This is becauuse 3.01 is the shortest string that gives the same floating point number. In other words, it's the shortest x so that
float(repr(x)) == x
where repr is the Python function that turns an object into a string (here it turns 3.0099... into 3.01) and float converts a string to a float.
There are obvisouly many strings that will result in the same internal float, but this is the shortest and therefore "probably" what you meant.
This feature was added in Python 2.7, as a backport of a Python 3.1 feature. It was discussed in Issue1580 and you should be able to find the code there and translate it into Java if you want.

Categories