Converting doubles to integers - java

I would like to convert a double (for example price with a value of 7.90) to an integer without losses! I am making a program that processes money, and I have to input them as doubles, and then convert them to integers so that I can proccess them and for example when I have 7.90 euros and i want to convert it to cents it will appear as 789 cents instead of 790! please help me :) thanks in advance

More specificaly the problem is that if it was a double it would originaly save 59.999999 instead of 60
Data type double variables are not capable of storing some values exactly, 60 is one of those values, so you should either a) use a different data type (float suffers from the same problems), or b) account for that using a tolerance in your checks.
In your case your variable shows 59.9999999 simply because its the closest that data type double can get to 60.

One possible solution:
Do not use floating point numbers at all. Instead of saying 1 Euro = 100 * 0.01 Euro, calculate with Cents: 1 Euro = 100 Cents. Calculate with Cents as integers and divide by 100 if you need Euros.
Another possibility:
When calculating big amounts of money, where it won't fit into an integer (maybe not even into a long), you use the BigInteger or BigDecimal classes. This is the usual and probably better approach when working with money. Precision and number of digits are arbitrary, but processing time and memory usage might be higher.

Related

NumberFormatException for input String - Java

I'm trying to round the cents of a value.
The rounding seems to work, but there's an exception:
double amount = 289.42;
String f= String.format("%.1f", amount);
System.out.println(new DecimalFormat("##0.00").format(Double.valueOf(f)));
This is the error: java.lang.NumberFormatException: For input string: "289,4"
Your question posits an impossibility.
Here's the thing: You cannot represent currency amounts with double. At all. Thus, 'how do I render these cents-in-a-double appropriately' isn't a sensible concept in the first place. Because cents cannot be stored in a double.
The problem lies in what double are, fundamentally. double is a numeric storage system that is defined to consist of exactly 64 bits. That's a problem right there: It's a mathematical fact that 64 bits can store at most 2^64 unique things, because, well, math. Think about it.
The problem is, There are in fact an infinite amount of numbers between 0 and 1, let alone between -infinity and +infinity which double would appear to represent. So, how do you square that circle? How does one represent one specific value chosen from an infinite amount of infinities, with only 64 bits?
The answer is simple. You don't.
doubles do not in fact store arbitrary values at all. And that is why you cannot use them to store currencies. Instead, take the number line and mark off slightly less than 2^64 specific values on it. We'll call these 'the blessed numbers'. A double can only store blessed numbers. They can't store anything else. In addition, any math you do to doubles is silently rounded to the nearest blessed value as doubles can't store anything else. So, 0.1 + 0.1? Not actually 0.2. Instead, 0.1 isn't even blessed, so that's really round-to-blessed(0.1) + round-to-blessed(0.1), so actually that's 0.0999999999975 + 0.0999999999975 = 0.2000000000018 or whatever. The blessed numbers are distributed unequally - there are a ton of blessed numbers in the 0-1 range, and as you move away from the 0, the distance between 2 blessed numbers grows larger and larger. Their distribution makes sense, but, computers count in binary, so they fall on neat boundaries in binary, not in decimal (0.1 looks neat in decimal. It's similar to 1 divided by 3, i.e. endlessly repeating, and therefore not precisely representable no matter how many bits you care to involve, in binary).
That rounding is precisely what you absolutely don't want to happen when representing currency. You don't want a cent to randomly appear or disappear and yet that is exactly what will happen if you use double to store finance info.
Hence, you're asking about how to render 'cents in a double' appropriately but in fact that question cannot possibly be answered - you can't store cents in a double, hence, it is not possible to render it properly.
Instead..
Use cents-in-int
The easiest way to do currency correctly is to first determine the accepted atomary unit for your currency, and then store those, in long or int as seems appropriate. For euros, that's eurocents. For bitcoin, that's satoshis. For yen, it's just yen. For dollars, its dollarcents. And so on.
$5.45 is best represented as the int value 545. Not as the double value 5.45, because that's not actually a value a double can represent.
Why do doubles show up as 0.1?
Because System.out.println knows that doubles are wonky and that you're highly likely to want to see 0.1 and not 0.09999999999991238 and thus it rounds inherently. That doesn't magically make it possible to use double to represent finance amounts.
I need to divide, or multiply by complex factors
Division for currency is always nasty. Imagine a cost of 100 dollars that needs to be paid by each 'partner' in a coop. The coop has 120 shares, and each partner has 40 shares, so each partner must pay precisely 1/3 of the cost.
Now what? 100 dollars does not neatly divide into threes. You can't very well charge everybody 33 dollars, 33 cents, and a third of a cent. You could charge everybody 33.33, but now the bank needs to eat 1 cent. You could also charge everybody 33.34, and the bank gets to keep the 2 cents. Or, you could get a little creative, and roll some dice to determine 'the loser'. The loser pays 33.34, the other 2 pay 33.33.
The point is: There is no inherently correct answer. Each situation has its own answer. Hence, division in general is impossible without first answering that question. There is no solving this problem unless you have code that knows how to apply the chosen 'division' algorithm. a / b cannot be used in any case (as the operation has at least 3 params: The dividend, the divisor, and the algorithm to apply to it).
For foreign exchange, 'multiply by this large decimal value' comes up a lot. You can store arbitrary precision values exactly using the java.math.BigDecimal class. However, this is not particularly suitable for storing currencies (all multiplication-by-a-factor will mean the BDs grow ever larger, they still can't divide e.g. 1 by 3 (anything that has repeating digits), and they don't solve the more fundamental issue: Any talk with other systems, such as a bank, can't deal with fractions of atomary units). Stick with BD-space math (as that is perfect, though, can throw exceptions if you divide, and grows ever slower and more complicated over time), until the system you are programming for enforces a rounding to atomary units, at which point, you round, resetting the growth. If you never need to multiply by fractions this doesn't come up, and there's no need to use BigDecimal for anything currency related.
How do I format cents-in-a-long?
String.format("€%d.%02d", cents / 100, cents % 100);
It gets very slightly more complicated for negative numbers (% returns negative values, so you need to do something about this. Math.abs can help), but not very.
cents / 100 gives you the "whole" part when you integer-divide by 100, and % 100 gives you the remainder, which precisely boils down to 'euros' and 'eurocents'.

Is it sufficient to convert a double to a BigDecimal just before addition to retain original precision?

We are solving a numeric precision related bug. Our system collects some numbers and spits their sum.
The issue is that the system does not retain the numeric precision, e.g. 300.7 + 400.9 = 701.599..., while expected result would be 701.6. The precision is supposed to adapt to the input values so we cannot just round results to fixed precision.
The problem is obvious, we use double for the values and addition accumulates the error from the binary representation of the decimal value.
The path of the data is following:
XML file, type xsd:decimal
Parse into a java primitive double. Its 15 decimal places should be enough, we expect values no longer than 10 digits total, 5 fraction digits.
Store into DB MySql 5.5, type double
Load via Hibernate into a JPA entity, i.e. still primitive double
Sum bunch of these values
Print the sum into another XML file
Now, I assume the optimal solution would be converting everything to a decimal format. Unsurprisingly, there is a pressure to go with the cheapest solution. It turns out that converting doubles to BigDecimal just before adding a couple of numbers works in case B in following example:
import java.math.BigDecimal;
public class Arithmetic {
public static void main(String[] args) {
double a = 0.3;
double b = -0.2;
// A
System.out.println(a + b);//0.09999999999999998
// B
System.out.println(BigDecimal.valueOf(a).add(BigDecimal.valueOf(b)));//0.1
// C
System.out.println(new BigDecimal(a).add(new BigDecimal(b)));//0.099999999999999977795539507496869191527366638183593750
}
}
More about this:
Why do we need to convert the double into a string, before we can convert it into a BigDecimal?
Unpredictability of the BigDecimal(double) constructor
I am worried that such a workaround would be a ticking bomb.
First, I am not so sure that this arithmetic is bullet proof for all cases.
Second, there is still some risk that someone in the future might implement some changes and change B to C, because this pitfall is far from obvious and even a unit test may fail to reveal the bug.
I would be willing to live with the second point but the question is: Would this workaround provide correct results? Could there be a case where somehow
Double.valueOf("12345.12345").toString().equals("12345.12345")
is false? Given that Double.toString, according to javadoc, prints just the digits needed to uniquely represent underlying double value, so when parsed again, it gives the same double value? Isn't that sufficient for this use case where I only need to add the numbers and print the sum with this magical Double.toString(Double d) method? To be clear, I do prefer what I consider the clean solution, using BigDecimal everywhere, but I am kind of short of arguments to sell it, by which I mean ideally an example where conversion to BigDecimal before addition fails to do the job described above.
If you can't avoid parsing into primitive double or store as double, you should convert to BigDecimal as early as possible.
double can't exactly represent decimal fractions. The value in double x = 7.3; will never be exactly 7.3, but something very very close to it, with a difference visible from the 16th digit or so on to the right (giving 50 decimal places or so). Don't be mislead by the fact that printing might give exactly "7.3", as printing already does some kind of rounding and doesn't show the number exactly.
If you do lots of computations with double numbers, the tiny differences will eventually sum up until they exceed your tolerance. So using doubles in computations where decimal fractions are needed, is indeed a ticking bomb.
[...] we expect values no longer than 10 digits total, 5 fraction digits.
I read that assertion to mean that all numbers you deal with, are to be exact multiples of 0.00001, without any further digits. You can convert doubles to such BigDecimals with
new BigDecimal.valueOf(Math.round(doubleVal * 100000), 5)
This will give you an exact representation of a number with 5 decimal fraction digits, the 5-fraction-digits one that's closest to the input doubleVal. This way you correct for the tiny differences between the doubleVal and the decimal number that you originally meant.
If you'd simply use BigDecimal.valueOf(double val), you'd go through the string representation of the double you're using, which can't guarantee that it's what you want. It depends on a rounding process inside the Double class which tries to represent the double-approximation of 7.3 (being maybe 7.30000000000000123456789123456789125) with the most plausible number of decimal digits. It happens to result in "7.3" (and, kudos to the developers, quite often matches the "expected" string) and not "7.300000000000001" or "7.3000000000000012" which both seem equally plausible to me.
That's why I recommend not to rely on that rounding, but to do the rounding yourself by decimal shifting 5 places, then rounding to the nearest long, and constructing a BigDecimal scaled back by 5 decimal places. This guarantees that you get an exact value with (at most) 5 fractional decimal places.
Then do your computations with the BigDecimals (using the appropriate MathContext for rounding, if necessary).
When you finally have to store the number as a double, use BigDecimal.doubleValue(). The resulting double will be close enough to the decimal that the above-mentioned conversion will surely give you the same BigDecimal that you had before (unless you have really huge numbers like 10 digits before the decimal point - the you're lost with double anyway).
P.S. Be sure to use BigDecimal only if decimal fractions are relevant to you - there were times when the British Shilling currency consisted of twelve Pence. Representing fractional Pounds as BigDecimal would give a disaster much worse than using doubles.
It depends on the Database you are using. If you are using SQL Server you can use data type as numeric(12, 8) where 12 represent numeric value and 8 represents precision. similarly, for my SQL DECIMAL(5,2) you can use.
You won't lose any precision value if you use the above-mentioned datatype.
Java Hibernate Class :
You can define
private double latitude;
Database:

float or double when dealing with an account transaction and money

I have been wondering what to use out of floats and doubles in my simple code problem. It will be in regards to money where the user will be able input amount of money deposits and print out current balance. The amount wont be too large (which i think a double is great for) but more importantly i want it to show to two decimals.
i am currently using float but only shows to one decimal and I too can not add an amount with two decimal places. Is there anyway to limit the amount added and restrict it to have only 2 decimal places? Maybe a simple condition i can add.
You should not use float or double for financial calculations; roundoff errors can accumulate in unexpected places. If you don't need more resolution than $0.01, use int or long and do all calculations in cents. Convert between cents and dollars (or whatever currency you're using) only on input and output.
If you need more resolution than $0.01 (say, for intermediate calculations), then you should use BigDecimal numbers. It's designed for applications like financial calculations where you need very high precision.

Precision nightmare in Java and SQL Server

I've been struggling with precision nightmare in Java and SQL Server up to the point when I don't know anymore. Personally, I understand the issue and the underlying reason for it, but explaining that to the client half way across the globe is something unfeasible (at least for me).
The situation is this. I have two columns in SQL Server - Qty INT and Price FLOAT. The values for these are - 1250 and 10.8601 - so in order to get the total value its Qty * Price and result is 13575.124999999998 (in both Java and SQL Server). That's correct. The issue is this - the client doesn't want to see that, they see that number only as 13575.125 and that's it. On one place they way to see it in 2 decimal precision and another in 4 decimals. When displaying in 4 decimals the number is correct - 13575.125, but when displaying in 2 decimals they believe it is wrong - 13575.12 - should instead be 13575.13!
Help.
Your problem is that you are using floats. On the java side, you need to use BigDecimal, not float or double, and on the SQL side you need to use Decimal(19,4) (or Decimal(19,3) if it helps jump to your precision level). Do not use the Money type because math on the Money type in SQL causes truncation, not rounding. The fact that the data is stored as a float type (which you say is unchangeable) doesn't affect this, you just have to convert it at first opportunity before doing math on it.
In the specific example you give, you need to first get the 4 decimal precision number and put it in a BigDecimal or Decimal(19,4) as the case may be, and then further round it to 2 decimal precision. Then (if you are rounding up) you will get the result you want.
Use BigDecimal. Float is not an approciate type to represent money. It will handle the rounding properly. Float will always produce rounding errors.
For storing monetary amounts floating point values are not the way to go. From your description I would probably handle amounts as long integers with as value the monetary amount multiplied by 10^5 as database storage format.
You need to be able to handle calculations with amounts that do not loose precision, so here again floating point is not the way to go. If the total sums between debit and credit are off by 1 cent in a ledger, the ledger fails in the eyes of financial people, so make sure your software operates in their problem domain, not yours. If you can not use existing classes for monetary amounts, you need to build your own class that works with amount * 10^5 and formats according to the precision wanted only for input and output purposes.
Don't use the float datatype for
price. You should use "Money" or
"SmallMoney".
Here's a reference for [MS SQL
DataTypes][1].
[1]:
http://webcoder.info/reference/MSSQLDataTypes.html
Correction: Use Decimal(19,4)
Thanks Yishai.
I think I see the problem.
10.8601 cannot be represented perfectly, and so while the rounding to 13575.125 works OK it's difficult to get it to round to .13 because adding 0.005 just doesn't quite get there. And to make matters worse, 0.005 doesn't have an exact representation either, so you end up just slightly short of 0.13.
Your choices are then to either round twice, once to three digits and then once to 2, or do a better calculation to start with. Using long or a high precision format, scale by 1000 to get *.125 to *125. Do the rounding using precise integers.
By the way, it's not entirely correct to say one of the endlessly repeated variations on "floating point is inaccurate" or that it always produces errors. The problem is that the format can only represent fractions that you can sum negative powers of two to create. So, of the sequence 0.01 to 0.99, only .25, .50, and .75 have exact representations. Consequently, FP is best used, ironically, by scaling it so that only integer values are used, then it is as accurate as integer datatype arithmetic. Of course, then you might as well have just used fixed point integers to start with.
Be careful, scaling, say, 0.37 to 37 still isn't exact unless rounded. Floating point can be used for monetary values but it's more work than it is worth and typically the necessary expertise isn't available.
The FLOAT data type can't represent fractions accurately because it is base2 instead of base10. (See the convenient link :) http://gregs-blog.com/2007/12/10/dot-net-decimal-type-vs-float-type/).
For financial computations or anything that requires fractions to be represented accurately, the DECIMAL data type must be used.
If you can't fix the underlying database you can fix the java like this:
import java.text.DecimalFormat;
public class Temp {
public static void main(String[] args) {
double d = 13575.124999999;
DecimalFormat df2 = new DecimalFormat("#.##");
System.out.println( " 2dp: "+ Double.valueOf(df2.format(d)) );
DecimalFormat df4 = new DecimalFormat("#.####");
System.out.println( " 4dp: "+Double.valueOf(df4.format(d)) );
}
}
Although you shouldn't be storing the price as a float in the first place, you can consider converting it to decimal(38, 4), say, or money (note that money has some issues since results of expressions involving it do not have their scale adjusted dynamically), and exposing that in a view on the way out of SQL Server:
SELECT Qty * CONVERT(decimal(38, 4), Price)
So, given that you can't change the database structure (which would probably be the best option, given that you are using a non-fixed-precision to represent something that should be fixed/precise, as many others have already discussed), hopefully you can change the code somewhere. On the Java side, I think something like #andy_boot answered with would work. On the SQL side, you basically would need to cast the non-precise value to the highest precision you need and continue to cast down from there, basically something like this in the SQL code:
declare #f float,
#n numeric(20,4),
#m money;
select #f = 13575.124999999998,
#n = 13575.124999999998,
#m = 13575.124999999998
select #f, #n, #m
select cast(#f as numeric(20,4)), cast(cast(#f as numeric(20,4)) as numeric(20,2))
select cast(#f as money), cast(cast(#f as money) as numeric(20,2))
You can also do a DecimalFormat and then round using it.
DecimalFormat df = new DecimalFormat("0.00"); //or "0.0000" for 4 digits.
df.setRoundingMode(RoundingMode.HALF_UP);
String displayAmt = df.format((new Float(<your value here>)).doubleValue());
And I agree with others that you should not be using Float as a DB field type to store currency.
If you can't change the database to a fixed decimal datatype, something you might try is rounding by taking truncate((x+.0055)*10000)/10000. Then 1.124999 would "round" to 1.13 and give consistent results. Mathematically this is unreliable, but I think it would work in your case.

AlmostEqual2sComplement or BigDecimal Scaling

What would be a better approach to compare two floating point values - using Epsilon or a Big Decimal comparison with scaling?
For instance, your data would range from 0.00 - 49,999.99?
For a data range of 0 through 50000 with 2 decimal digits, I would use the built-in types with an epsilon.
It really depends on what you're doing with the numbers. If you're just adding or subtracting, you would have to process a large number of numbers to have the floating point errors accumulate to 0.01 for your data range.
Let me explain: let's say your float has 17 significant digits. That means your error for numbers up to 49999.99 may be about 0.000000000001. You would have to add some 10 billion numbers for that cumulative error to reach one cent. Multiplication and division would accumulate that error faster but it would still take quite a while.
If you don't understand how that works (and performance is not the be-all and end-all in your application), use BigDecimal for safety. Built-in types should be a lot faster.
Also, when processing currency, there is no reason you cannot simply store an integer representing cents instead of dollars, so that you effectively have a fixed-point representation. eg $4.09 gets stored as 409 and so on. (You may also choose to store tenths of a cent ie 4090 or some other constant fractional precision.) You will be able to add and subtract an infinite number of times without losing precision.
For calculations such as interest, perform the calculation with floating-point numbers and then simply round to the necessary precision before storing. The interest calculation itself will have the necessary precision and you will consistently round to the same number of decimal places each period, which is usually what you want in financial calculations (I've never seen an institution that really wants to keep track of $0.00001 from one pay period to the next -- for legal reasons they will round their books to some specified precision.)
An ordinary signed int storing cents will let you represent up to $21,474,836.47 . You can use a long to store $92,233,720,368,547,758.07. For dealing with more than quadrillions of currency units (representing the US budget in Zim dollars?), use a BigDecimal.
You can learn more about floating point comparisons here:
What Every Computer Scientist Should Know About Floating-Point Arithmetic

Categories

Resources