3

I've got a time calculator that has worked reasonably well for a number of years. One thing that always bothered me, though, was that if one used fractional seconds, the results would fall victim to floating-point "errors". So, I recently switched to using this BigDecimal library.

Now, I'm getting math errors. Here's a simplified test case from a bug report I received today: 27436 / 30418 is returning 1 instead of the expected 0.9019659412190151.

To illustrate my problem, here's a Javascript console session in Chrome:

> first = 27436
27436
> second = 30418
30418
> first / second
0.9019659412190151   // expected result, but using JS numbers, not BigDecimals
> firstB = new BigDecimal(first.toString())
o
> secondB = new BigDecimal(second.toString())
o
> firstB / secondB
0.9019659412190151 // this is a JS number, not a BigDecimal, so it's susceptible to the problems associated with floating-point.
> firstB.divide(secondB)
o  // BigDecimal object
> firstB.divide(secondB).toString()
"1"  // huh? Why 1?
> firstB.divideInteger(secondB).toString()
"0"

As you can see, the divide() method isn't producing the result I expect. What do I need to do differently?

Update

Here are some more details, in response to the comments.

First, several people suggested that using BigDecimal was overkill. That might be, but I think that more details are necessary before making that decision. This app is a time calculator, so there are a couple of things that pushed me to switch to BigDecimal. First, because this is a calculator, it's important to show the user a correct answer. If the user enters 0.1 s + 0.2 s, they expect the answer to be 0.3 s, not the answer that Javascript will show them (0.30000000000000004).

I don't really want to limit the precision beyond what I can use in JS so that I can use integers, because I don't know the maximum precision my users need. Most never use fractional seconds, I think, but judging from the email I've received, some do. I'm currently storing all times internally as seconds.

Someone suggested that I store the numbers as exact fractions. Unfortunately, I don't know what that means. Perhaps it's because I don't know too much about math. I don't know enough to roll my own math library; that's why I'm using BigDecimal. It's been around for a long time, so I'm hesitant to say that my problem is due to a bug in BigDecimal. I suspect rather that it's a bug in the way I'm using it.

Finally, I'm not wedded to BigDecimal specifically. I'm open to other suggestions, provided that I can use them with my deficient math skills.

Scott Severance
  • 943
  • 10
  • 27
  • 1
    Maybe it has something to do with precision. Like, if the precision is too small it just rounds off to the nearest integer. Also, it seems like overkill to use BigDecimal simply because of floating point error. You could also just store the numbers as exact fractions. – karnok Jan 05 '12 at 04:23
  • Yeah, using BigDecimal looks like an overkill to me too. Especially if you are doing calculations on fixed-precision stuff like *time*, why don't you just use integers (seconds? milliseconds? ..?) and then do all the appropriate conversions..? – redShadow Jan 05 '12 at 04:33
  • 0.1 is a fairly big difference for a rounding-error, also the issue remains in firebug. you maybe want to file a bug report. – Samuel Herzog Jan 05 '12 at 05:25
  • 1
    http://stackoverflow.com/questions/588004/is-javascripts-math-broken – j08691 Jan 05 '12 at 18:18
  • @j08691: Could you explain why you posted that link and how it helps my situation? I already know the reason for floating point errors, as I explained in my post. I'm looking for a solution for my specific issue, and I can't see how your link solves my problem. – Scott Severance Jan 05 '12 at 18:22

1 Answers1

4

I haven't used BigDecimal in any production code yet but found this question interesting so I though I give it a try. You are right about the need for a MathContext as parameter to the division function. Here is what I did, based on your example:

console.log(firstB.divide(secondB, new MathContext(100)).toString());

Creating a context that tells the BigDecimal to use 100 digits in scientific mode outputs:

0.9019659412190150568742192123085015451377473864159379314879347754618975606548754027220724570977710566

There's also options to control different output modes PLAIN, SCIENTIFIC and ENGINEERING + various rounding modes.

Full example on jsfiddle

Update: The default output format is SCIENTIFIC, not PLAIN. Examples here

Update 2: Created a tiny performance test here, looks like BigDecimal is about 10000 times slower than native javascript division.

eolsson
  • 12,567
  • 3
  • 41
  • 43
  • Thanks. After updating my copy of BigDecimal to the latest version, things started working. – Scott Severance Jan 06 '12 at 17:13
  • Well, performance isn't too critical. I'm only doing a single division operation. Altogether, my app does about five calculations in response to user input, so the difference in speed won't be noticeable. We're talking a difference probably of a few ms. – Scott Severance Jan 06 '12 at 19:23