0

I'm trying to write some code to add together cricket overs. Over numbering in cricket goes 0.1, 0.2, 0.3, 0.4, 0.5, 1.0, 1.1 etc and so just adding together decimals in a standard way doesn't work.

I've followed the advice provided in this thread - Php: when a number gets to 0.6 make it 1 to do the actual conversion, and when I throw a random number into the equation provided it returns as expected. However, when I use a number that I am generating from my database, it doesn't return the same thing.

I have tried to create some example code and output to explain the problem further. In this example the variable $xb is equal to 1.2 as you will see in the output.

Code:

echo "Var type - " . gettype($xb) . ' Value = ' . $xb . '<br />';
echo "Var type - " . gettype(1.2) . ' Value = 1.2<br />';

$overs = floor((1.2 * 10) / 6);
$balls = (1.2 * 10) - ($overs * 6);

$x_overs = floor(($xb * 10) / 6);
$x_balls = ($xb * 10) - ($x_overs * 6);

echo "Total overs = " . $overs . '.' . $balls . "<br />";
echo "Total x overs = " . $x_overs . '.' . $x_balls . "<br />";

Output:

Var type - double Value = 1.2
Var type - double Value = 1.2
Total overs = 2.0
Total x overs = 1.6

I'm sure I must be doing something fundamentally stupid or be misunderstanding something basic, but I've been staring at it for an hour and can't see it. Can someone put me out my misery please?

Community
  • 1
  • 1
bradfields
  • 71
  • 1
  • 10
  • Use base6 instead. http://php.net/manual/en/function.base-convert.php – thepratt Jul 27 '14 at 17:22
  • 2
    I can't reproduce your problem, so I suspect that the value you get isn't actually 1.2, but more something like [1.1999999999999985](http://stackoverflow.com/questions/3730019/why-not-use-double-or-float-to-represent-currency/3730040#3730040). – zneak Jul 27 '14 at 17:22
  • @nuc could you provide an example of using base_convert? I did try that but didn't have any luck. Some example numbers for you to work with. 2 + 4 + 1.5 + 5 + 1.4 + 3.3 + 5 = 22.2, it should equal 23 in cricket overs. – bradfields Jul 27 '14 at 17:26
  • @zneak Urgh, I knew PHP was a bit dodgy with floats but didn't realise it was this crappy. Pretty much all I'm doing is adding together 0.5, 0.4 and 0.3 to get to my 1.2, but I'm stripping them out of the numbers in the comment above so guess I must be getting the rounding error there where I'm using fmod. – bradfields Jul 27 '14 at 17:32
  • @bradfields, it's not just PHP. Every single language or framework that uses native IEEE-754 floating-point support has this issue. You usually have to go out of your way to use precise base 10 floating-point arithmetic in languages that support both. – zneak Jul 27 '14 at 17:35

3 Answers3

3

I would suggest having two formats, one for display and one for processing. You'll need routines to convert between the two formats.

In the processing format, I'd suggest multiplying the units place by 6 and adding it to the tenths place. So "1.0" would be 6, "1.1" would be 7, and so on. This way, you can easily manipulate numbers in processing format, adding and subtracting them as desired. You can manipulate these numbers as integers, so there's no chance of floating point rounding messing you up.

To go from processing format to display format, divide by six using integer division. The result is the units place and the remainder is the tenths place. So 8 divided by 6 is 1 remainder 2. So an internal value of 8 corresponds with a display value of "1.2".

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • Thanks for your answer, if you think the problem is likely to be coming from some kind of rounding error then I guess this is probably going to be my most logical solution. Bit of a sh*t cos I'm going to have to do some major rewriting! :) – bradfields Jul 27 '14 at 17:30
  • @bradfields Cheap lesson -- always think carefully about data representation before writing code to manipulate that data. Representing inherently integer values as floating point numbers is almost always a bad idea. – David Schwartz Jul 27 '14 at 17:32
  • Thanks David, advice taken! – bradfields Jul 27 '14 at 17:39
2

Double arithmetic is inherently inaccurate. For this kind of manipulation, I recommend that you use integers instead.

Right now, I suspect that you have your decimal number and increment by 0.1 each time you need to augment the value. Since 0.1 can't be accurately represented as a base 2 floating point value, your 1.2 isn't actually 1.2, it's a value slightly inferior to that, but it's close enough that PHP chooses to round it up when displaying it. You can verify this hypothesis easily enough: is $xb < 1.2?

Instead of storing the numbers as 0.1, 0.2, 0.3, 0.4, 0.5, 1.0, ... (of which only 1.0 can be exactly represented as a double, and you won't get to exactly 1 by adding 0.1 10 times or even just 6 times, following the numbering convention), you should increment an integer and format somewhat like that:

$x = 8;
echo floor($x / 6) . "." . ($x % 6);

I have no idea how cricket works, but if you ever need to add one raw over, you can add 6. If you need to "round up" to overs, you can go to the next multiple of six.

Community
  • 1
  • 1
zneak
  • 134,922
  • 42
  • 253
  • 328
0

Without an error analysis, do not use discontinuous functions, such as floor, on values that mathematically are a discontinuity point (here, floor on 2). So, either do an error analysis, then you can correct the argument of the discontinuous function, e.g. floor(x+0.01) instead of floor(x), or use an exact arithmetic (integers, rationals).

vinc17
  • 2,829
  • 17
  • 23