I’ve tried looking for definitive answers but the only difference I see seems to be based on the precision of the numbers?
Which data type is larger and which one is smaller?
I am learning Java, and am confuse on when to choose double or float for my real numbers or int. It feels like, it doesn’t matter because from my limited experience (with Java) both of them deliver the same results, but I don’t want to go further down the learning curve with Java and have a bad habit of using either messing up my code, and not having a clue as to why. So, when should you use float and double?
Videos
let’s say we have: long l1 = 9L; double d1 = 3.5;
double result = l1 + d1;
Just learned that arithmetic operations are done only on: int, long,double, float.
And I know about promoting data type to higher by occupation in memory. But both long and double occupies 8 bytes. So why the result is double?
Everyone says that BigDecimal should be used when dealing with money but it’s much slower and takes more memory than double. I would think this would be especially important in high frequency low-latency applications like trading. Do people actually use BigDecimal in such systems or do they use doubles with some kind of workaround to handle the precision issue?
Edit: I do have experience working on trading and risk systems and I see doubles used much more often than BigDecimal so I was curious to see if this is more common in actual practice. Most of the systems I worked on only need precision to the penny so I wonder if that’s the reason?
Also, BigDecimal is a pain to use and code written with it look much uglier than plain doubles.
If you are storing integers, use Long. Your statement that "Advantage of Using Double is that it gives a more wider range for storing Whole Numbers" is incorrect. Both are 64 bits long, but double has to use some bits for the exponent, leaving fewer bits to represent the magnitude. You can store larger numbers in a double but you will lose precision.
In other words, for numbers larger than some upper bound you can no longer store adjacent "whole numbers"... given an integer value above this threshold, the "next" possible double will be more than 1 greater than the previous number.
For example
public class Test1
{
public static void main(String[] args) throws Exception
{
long long1 = Long.MAX_VALUE - 100L;
double dbl1 = long1;
long long2 = long1+1;
double dbl2 = dbl1+1;
double dbl3 = dbl2+Math.ulp(dbl2);
System.out.printf("%d %d\n%f %f %f", long1, long2, dbl1, dbl2, dbl3);
}
}
This outputs:
9223372036854775707 9223372036854775708
9223372036854776000.000000 9223372036854776000.000000 9223372036854778000.000000
Note that
- The double representation of Long.MAX_VALUE-100 does NOT equal the original value
- Adding 1 to the double representation of Long.MAX_VALUE-100 has no effect
- At this magnitude, the difference between one double and the next possible double value is 2000.
Another way of saying this is that long has just under 19 digits precision, while double has only 16 digits precision. Double can store numbers larger than 16 digits, but at the cost of truncation/rounding in the low-order digits.
If you need more than 19 digits precision you must resort to BigInteger, with the expected decrease in performance.
This looks like the wrong battle:
From the Java Tutorial
The long data type is a 64-bit signed two's complement integer. It has a minimum value of -9,223,372,036,854,775,808 and a maximum value of 9,223,372,036,854,775,807 (inclusive).
That's pretty close to 19 significant digits
From Wikipedia
This gives from 15 - 17 significant decimal digits precision.
So, despite its apparent "superiority" Double will serve you worse than Long. And I'm merely guessing here, but intuitively I'd say serialization/deserialization of floating point types are costlier operations than the same operations on integral data types, but even if there are differences they will be quite small on modern systems.
So, when working with integers, stick to Long.
The absolute quantity of information that you can store in 64 bit is of course the same.
What changes is the meaning you assign to the bits.
In an integer or long variable, the codification used is the same you use for decimal numbers in your normal life, with the exception of the fact that number two complement is used, but this doesn't change that much, since it's only a trick to gain an additional number (while storing just one zero instead that a positive and a negative).
In a float or double variable, bits are split in two kinds: the mantissa and the exponent. This means that every double number is shaped like XXXXYYYYY where it's numerical value is something like XXXX*2^YYYY. Basically you decide to encode them in a different way, what you obtain is that you have the same amount of values but they are distribuited in a different way over the whole set of real numbers.
The fact that the largest/smallest value of a floating number is larger/smaller of the largest/smalles value of a integer number doesn't imply anything on the amount of data effectively stored.
A double can store a larger number by having larger intervals between the numbers it can store, essentially. Not every integer in the range of a double is representable by that double.
More specifically, a double has one bit (S) to store sign, 11 bits to store an exponent E, and 52 bits of precision, in what is called the mantissa (M).
For most numbers (There are some special cases), a double stores the number (-1)^S * (1 + (M * 2^{-52})) * 2^{E - 1023}, and as such, when E is large, changing M by one will make a much larger change in the size of the resulting number than one. These large gaps are what give doubles a larger range than longs.
With the possible exception of "short", which arguably is a bit of a waste of space-- sometimes literally, they're all horses for courses:
- Use an int when you don't need fractional numbers and you've no reason to use anything else; on most processors/OS configurations, this is the size of number that the machine can deal with most efficiently;
- Use a double when you need fractional numbers and you've no reason to use anything else;
- Use a char when you want to represent a character (or possibly rare cases where you need two-byte unsigned arithmetic);
- Use a byte if either you specifically need to manipulate a signed byte (rare!), or when you need to move around a block of bytes;
- Use a boolean when you need a simple "yes/no" flag;
- Use a long for those occasions where you need a whole number, but where the magnitude could exceed 2 billion (file sizes, time measurements in milliseconds/nanoseconds, in advanced uses for compacting several pieces of data into a single number);
- Use a float for those rare cases where you either (a) are storing a huge number of them and the memory saving is worthwhile, or (b) are performing a massive number of calculations, and can afford the loss in accuracy. For most applications, "float" offers very poor precision, but operations can be twice as fast -- it's worth testing this on your processor, though, to find that it's actually the case! [*]
- Use a short if you really need 2-byte signed arithmetic. There aren't so many cases...
[*] For example, in Hotspot on Pentium architectures, float and double operations generally take exactly the same time, except for division.
Don't get too bogged down in the memory usage of these types unless you really understand it. For example:
- every object size is rounded to 16 bytes in Hotspot, so an object with a single byte field will take up precisely the same space as a single object with a long or double field;
- when passing parameters to a method, every type takes up 4 or 8 bytes on the stack: you won't save anything by changing a method parameter from, say, an int to a short! (I've seen people do this...)
Obviously, there are certain API calls (e.g. various calls for non-CPU intensive tasks that for some reason take floats) where you just have to pass it the type that it asks for...!
Note that String isn't a primitive type, so it doesn't really belong in this list.
A java int is 32 bits, while a long is 64 bits, so when you need to represent integers larger than 2^31, long is your friend. For a typical example of the use of long, see System.currentTimeMillis()
A byte is 8 bits, and the smallest addressable entity on most modern hardware, so it is needed when reading binary data from a file.
A double has twice the size of a float, so you would usually use a double rather than a float, unless you have some restrictions on size or speed and a float has sufficient capacity.
A short is two bytes, 16 bits. In my opinion, this is the least necessary datatype, and I haven't really seen that in actual code, but again, it might be useful for reading binary file formats or doing low level network protocols. For example ip port numbers are 16 bit.
Char represents a single character, which is 16 bits. This is the same size as a short, but a short is signed (-32768 to 32767) while a char is unsigned (0 to 65535). (This means that an ip port number probably is more correctly represented as a char than a short, but this seems to be outside the intended scope for chars...)
For the really authorative source on these details, se the java language specification.
So i am learning java, and my mentor is a senior with deep roots in the field. Anyways on one of our weekly checkup calls he asked me a simple question whats the difference in primitive data types and is there a reason to use short over int.
Well i couldnt answer this novel question and so i went on searching and i couldnt find a proper answer for the second part. While most seemed to agree int would be faster than short, the opinions on just HOW much faster varied alot.
I saw this as a learning opportunity (Also i thought itd be interesting to start making videos about this kind of stuff i learn)
So i ran a few (albeit amateur) tests to see the differences. First i did just sums for int vs short with shorts being much slower. But i learned about blackholes and like jvm can sometimes over optimize your code etc so i kind of caved and got some help from claude for what mathematical equation would be best to see the differences. Also since bytes only go up to a few numbers i had to nest it 3 times in loops so that i had a long enough loop.
Also heres a short vid
Here are the results
along with the code (for the second bigger chart)
package com.yourcompany;
import org.openjdk.jmh.annotations.*;
import java.util.concurrent.TimeUnit;
(Scope.Thread)
(Mode.AverageTime)
(TimeUnit.MICROSECONDS)
(value = 1, warmups = 2)
(iterations = 3)
public class MyBenchmark {
// Using byte-sized loops (max value 127)
private static final byte OUTER_LOOPS = 32;
private static final byte MIDDLE_LOOPS = 16;
private static final byte INNER_LOOPS = 8;
u/Benchmark
public byte testByte() {
byte z = 42;
for (byte i = 0; i < OUTER_LOOPS; i++) {
for (byte j = 0; j < MIDDLE_LOOPS; j++) {
for (byte k = 0; k < INNER_LOOPS; k++) {
int t = (z * 31) + i + j + k;
z = (byte) (t ^ (t >>> 8));
z = (byte) ((z / 7) + (z % 64));
}
}
}
return z;
}
u/Benchmark
public short testShort() {
short z = 42;
for (byte i = 0; i < OUTER_LOOPS; i++) {
for (byte j = 0; j < MIDDLE_LOOPS; j++) {
for (byte k = 0; k < INNER_LOOPS; k++) {
int t = (z * 0x9E37) + i + j + k;
z = (short) (t ^ (t >>> 16));
z = (short) ((z / 7) + (z % 1024));
}
}
}
return z;
}
u/Benchmark
public int testInt() {
int z = 42;
for (byte i = 0; i < OUTER_LOOPS; i++) {
for (byte j = 0; j < MIDDLE_LOOPS; j++) {
for (byte k = 0; k < INNER_LOOPS; k++) {
int t = (z * 0x9E3779B9) + i + j + k;
z = (t ^ (t >>> 16));
z = (z / 7) + (z % 1024);
}
}
}
return z;
}
u/Benchmark
public long testLong() {
long z = 42L;
for (byte i = 0; i < OUTER_LOOPS; i++) {
for (byte j = 0; j < MIDDLE_LOOPS; j++) {
for (byte k = 0; k < INNER_LOOPS; k++) {
long t = (z * 0x9E3779B97F4A7C15L) + i + j + k;
z = (t ^ (t >>> 32));
z = (z / 7) + (z % 4096);
}
}
}
return z;
}
u/Benchmark
public float testFloat() {
float z = 42.0f;
for (byte i = 0; i < OUTER_LOOPS; i++) {
for (byte j = 0; j < MIDDLE_LOOPS; j++) {
for (byte k = 0; k < INNER_LOOPS; k++) {
float t = (z * 1.618033988749f) + i + j + k;
z = t * t;
z = (z / 7.0f) + (z % 1024.0f);
}
}
}
return z;
}
u/Benchmark
public double testDouble() {
double z = 42.0;
for (byte i = 0; i < OUTER_LOOPS; i++) {
for (byte j = 0; j < MIDDLE_LOOPS; j++) {
for (byte k = 0; k < INNER_LOOPS; k++) {
double t = (z * 1.618033988749894848) + i + j + k;
z = t * t;
z = (z / 7.0) + (z % 4096.0);
}
}
}
return z;
}
u/Benchmark
public char testChar() {
char z = 42;
for (byte i = 0; i < OUTER_LOOPS; i++) {
for (byte j = 0; j < MIDDLE_LOOPS; j++) {
for (byte k = 0; k < INNER_LOOPS; k++) {
int t = (z * 0x9E37) + i + j + k;
z = (char) (t ^ (t >>> 16));
z = (char) ((z / 7) + (z % 512));
}
}
}
return z;
}
}