1

Ok, so we all know of the floating point number problem, such as:

0.23 - 1 + 1 = 0.22999999999999998

And since in javascript, unlike other languages, all numbers are actually floating points and there's no int/decimal there are all kinds of libraries and workarounds such as BigDecimal to handle this problem. This is best discussed here.

I was creating a "numeric spinner" control that supports floating point numbers, and obviously I wouldn't want the user to click "up" and then "down" and get a different number from what he started with, so I tried writing a workaround - a "addPrecise" method of sorts.

My idea was the following:

  1. Take the two numbers I'm about to add, and figure out their "precision" - how many digits they have after the decimal point.
  2. Add the two numbers as floats
  3. Apply toFixed() on the result with the max precision of the two

For example:

float #1: 1.23
float #2: -1

adding them normally would result in 0.22999999999999998 but if I'm taking the maximal number of decimal places, which is #1's 2 digits, and apply toFixed(2) I get 0.23 as I wanted.

I've done this with the following piece of code but I'm not happy with it.

function addPrecise(f1, f2){
    var floatRegex = /[^\d\-]/;
    var f1p =  floatRegex.exec(f1.toString()) ? f1.toString().split(floatRegex.exec(f1.toString()))[1].length : 0;
    var f2p =  floatRegex.exec(f2.toString()) ? f2.toString().split(floatRegex.exec(f2.toString()))[1].length : 0;
    var precision = Math.max(f1p,f2p);
    return parseFloat((parseFloat(f1) + parseFloat(f2)).toFixed(precision));
}

(It's worth noting that I'm using the regex to find the 'floating point' because in other locales it might be a comma instead of a period. I'm also taking into account the possibility that I got an Int (with no point) in which case it's precision is 0.)

Is there a cleaner/simpler/better way to do this?

Edit: I'd also like to point out that finding a reliable way to extract the number of decimal digits is also part of the question. I'm unsure about my method there.

Community
  • 1
  • 1
motig88
  • 402
  • 5
  • 16
  • [Code Review SE](http://codereview.stackexchange.com/) may actually be better suited for this type of question. – Jonathan Lonowski Aug 14 '13 at 08:59
  • I'm not sure, since it's a question of "Am I doing this wrong" both code-wise and thought-wise. The problem has been discussed here before, but usually the answers are either "you can't" or "use BigDecimal". I can't use BigDecimal so I'm trying to take another approach.. which might be stupid. – motig88 Aug 14 '13 at 09:02
  • Well, Code Review is for [more than just formatting](http://codereview.stackexchange.com/help/on-topic). But, as it's currently written, this question is rather open-ended and states that it's in search of opinion. SO isn't really the [right place for either](http://stackoverflow.com/help/dont-ask). – Jonathan Lonowski Aug 14 '13 at 09:50
  • 1
    There are certainly better ways to round floating-point than parsing strings. But first, we should find out what the real problem is and why you are using floating point. What operations can be performed on this control (click up, click down, something else?), and how many states it can have? If it is just some dial that can be turned to some number of positions, why not just use integer arithmetic? – Eric Postpischil Aug 14 '13 at 11:09
  • It's a "spinner" control that allows a user to click up/down (which adds/subtracts by a predefined step) or type/paste a number. I need to support floating point numbers as well - the increment step can be anything (such as 1, 0.02, whatever) and the number can be any decimal including negative numbers. That's why integers don't cut it. – motig88 Aug 14 '13 at 11:23
  • @motig88: The number can be **any** decimal numeral? With twenty digits? Ten thousand? Probably you just need to allow a few digits, yes? Then all you need for a simple problem like this is to use an integer type, read the user’s input, and scale it from a decimal to an integer. E.g., decide you are going to allow four digits after the decimal point, multiply each input by 10,000, and work with them as integers. You can either do all the work with integer arithmetic or use floating-point to assist with the input/output, as long as you round correctly when converting to/from integer. – Eric Postpischil Aug 14 '13 at 13:09

1 Answers1

1

An appropriate solution for this problem is:

  1. Determine how many digits, say d, are needed after the decimal point.
  2. Read inputs from the user and convert them to integers scaled by 10d.
  3. Do all arithmetic using the integers.
  4. When displaying values for the user, divide them by 10d for display.

More details:

  1. If all work is in whole units of user-entered data, then the number of digits needed after the decimal point, d, is the same as the number of digits to be accepted from the user. (If one were going to do some arithmetic with fractions of the step size, for example, then more digits might be needed.)
  2. When the user enters input, it should be converted to the scaled integers. Note that the user enters a string (not a floating-point number; it is just a string when the user types it), and the string should be a numeral (characters that represent a number). One way to process this input is to call library routines to convert this numeral to a floating-point number, then multiply by 10d, then round the floating-point result to the nearest integer (to compensate for floating-point rounding errors). For numbers of reasonable size, this will always produce exactly the desired result; floating-point rounding errors will not be a problem. (Excessively large user input should be rejected.) Another way to process this input is to write your own code to convert the numeral directly to a scaled integer, avoiding floating-point entirely.
  3. As long as only addition, multiplication, and subtraction are used (e.g., multiplying the step size by a number of steps and adding to a prior value), and the integer range is not exceeded, then all arithmetic is exact; there are no rounding errors. If division is used, this approach must be reconsidered.
  4. As with reading input, displaying output can be performed either with an assist from floating point and library routines or by writing your own code to convert directly. To use floating-point, convert the integer to floating-point, divide by 10d, and use a library routine to format it with no more than d digits after the decimal place. (Using default format conversions in library routines might result in more than d digits displayed. If only d digits are displayed, and d is a reasonably small number, then floating-point rounding errors that occur during the formatting will not be visible.)

It is possible to implement all of the above using only floating-point, as long as care is taken to ensure that the arithmetic uses entirely integers (or other values that are exactly representable in the floating-point format) within reasonable bounds.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • This helped me break up the issue into three challenges: 1. Determine the number of decimal digits 2. Parse/convert numbers correctly 3. Perform a precise calculation. It seems my solution takes one (ugly) approach to these three, and your's provides a different path for number 2 & 3. Could you explain why this way is better? I too feel that all that string-hacking business is no good but will conversion to Int provide any advantages in terms of performance, reliability? – motig88 Aug 14 '13 at 13:37
  • Also, I've just played with this and it seems that: `2363457234.1234567 * 10000000 = 23634572341234564` which is a problem (the last digit is inaccurate) – motig88 Aug 14 '13 at 13:40
  • @motig88: You can use either integer or floating-point. In many situations, there is little difference in performance, especially when you are doing only a small amount of processing to manage a user interface like this. The important thing is that the arithmetic be exact (or well understood and controlled). Some people are more comfortable with integer arithmetic, because they understand where it is exact and are uncertain about floating-point. Another consideration could be the range (whether the values you need fit into a 32-bit float, a 32-bit integer, a 64-bit float, et cetera). – Eric Postpischil Aug 14 '13 at 13:55
  • @motig88: Regarding 2363457234.1234567, see this text in the details for item 2: “Excessively large user input should be rejected.” Why would your control need a magnitude of two billion with a precision of one ten-millionth? That is enough precision to represent the distance to the sun to within 15 microns. You cannot possibly need that. – Eric Postpischil Aug 14 '13 at 13:58
  • @motig88 In addition to Eric Postpischil's question about why you need such precision, if you really do need exactness for 17 or more significant digits, your only option may be to find an extended decimal arithmetic package. IEEE 754 64-bit binary float has effectively 53 significant bits, equivalent to just under 16 significant decimal digits. – Patricia Shanahan Aug 14 '13 at 14:28
  • Why do I need it? The short answer is because I've been told not to limit the user. The longer answer is because we don't know what kind of data will be filtered by that control, it's up to the users. It does make sense to me to just limit that precision but I'm not sure I'll get approval for that, in which case my ugly solution will do the trick. I'm accepting this answer due to it's level of detail and effort put into it. Thank you. – motig88 Aug 15 '13 at 08:00