SQLite User Forum

BUG: round(0.15,1) = 0.1; v3.45.1
Login

BUG: round(0.15,1) = 0.1; v3.45.1

(1) By anonymous on 2024-06-09 12:15:49 [link] [source]

SELECT round(0.15,1);

result = 0.1

should be 0.2

Linux x85_64

am i missing something?

(2) By Harald Hanche-Olsen (hanche) on 2024-06-09 13:12:22 in reply to 1 [link] [source]

Note that 0.15 does not have a finite representation in binary, so the floating point number resulting from parsing 0.15 will be either slightly smaller than 0.15 or slightly larger. Your finding indicates the former.

(3) By anonymous on 2024-06-09 13:38:39 in reply to 1 [link] [source]

sqlite> select round(0.49999999999999999); 1.0

sqlite> select round(0.4999999999999999); 0.0

(4) By Adrian Ho (lexfiend) on 2024-06-09 14:31:10 in reply to 1 [link] [source]

(5) By Richard Hipp (drh) on 2024-06-09 15:23:09 in reply to 1 [link] [source]

The number 0.15 cannot be represented exactly by an IEEE-754 double. The closest approximation is 5404319552844595 * 2-55 which works out to be 0.1499999999999999944488848768742172978818416595458984375 and that number founds down to 0.1.

(6) By anonymous on 2024-06-09 15:47:04 in reply to 5 [link] [source]

I use John Herbster routines created 2001.09.19. I overload round function but maybe it could be part of sqlite. Original file if DecimalRounding_JH1.pas.

These routines round input values to fit as closely as possible to an output number with desired number of decimal fraction digits.

(7) By Larry Brasfield (larrybr) on 2024-06-09 18:28:52 in reply to 6 [link] [source]

You appear to be missing the thrust of Harald's and Richard's replies. Summarizing them: The results you have reported under "BUG" are not evidence of a bug; they are the correct results. Since they both explained what lies behind what seems to be anomalous to you, I will not replicate that. However, I suggest that you not think of something like "0.15" as representing a binary floating point number. It represents a decimal-radix number for which there is no exact binary representation.

(8) By anonymous on 2024-06-09 19:24:39 in reply to 7 [link] [source]

I'm not anonymous from first post. This rounding can be done and other developers have worked on that. Like I said look at John Herbster solution. It could be implemented in sqlite because is not always possible to define overload function.

(9) By Stephan Beal (stephan) on 2024-06-09 19:29:45 in reply to 8 [link] [source]

It could be implemented in sqlite...

Not without a behavior change which which may impact many thousands of applications and countless users. Backwards compatibility is, because of this project's unusually large user base, a top priority. Gratuitous backwards incompatibility is never an option.

(10) By anonymous on 2024-06-09 19:39:25 in reply to 9 [link] [source]

If you look at sqlite history changes like that can be implemented with using pragma if this is such a big problem. For me I have a solution for 14 years but I see many asking about this and it is so simple to do.

(11) By anonymous on 2024-06-09 21:18:04 in reply to 7 [link] [source]

The fact that the implementation uses IEEE 754 need not imply that SQL users should have to be aware of the intricacies of floating-point representations.

If SQL Fiddle is to be believed, for select round(0.15,1):

So SQLite is definitely an outlier in this case.

OP's suggestion of a pragma to introduce mathematical rounding behavior doesn't seem outlandish.

(12) By Bo Lindbergh (_blgl_) on 2024-06-10 06:47:39 in reply to 11 [link] [source]

Those other systems likely use (slow) decimal arithmetic to accomplish that.

mathematical rounding

Binary arithmetic is just as mathematical as decimal arithmetic.

(13) By Spindrift (spindrift) on 2024-06-10 07:15:37 in reply to 12 [link] [source]

You can presumably see why someone entering a decimal number, and then applying a rounding function with precision given as a decimal number in a domain where most other engines (apparently) treat this as a decimal calculation would be surprised by the result though?

This certainly fails the principle of least surprise.

There are excellent reasons why it is done like this, and important project integrity reasons why change is significant and due consideration.

However, I don't think this brushing off of such a problem is entirely without risk.

Perhaps a pragma is the answer. Perhaps a different rounding function. Perhaps a clear explanation about scaling the number before rounding it.

However, for a number presented as a decimal, this is clearly the wrong answer, irrespective of the hidden conversion to binary floating point which explains the reasons.

It's a reason, but a poor excuse.

(14) By Bo Lindbergh (_blgl_) on 2024-06-10 08:20:57 in reply to 13 [link] [source]

Once a number has been converted to binary floating point, it's too late. If you want unsurprising decimal rounding, you need to keep the data in some decimal format.

The decimal extension exists, but it only has a minimal set of operations. A decimal_round function would be easy to add, but a decimal_div function would not.

(15) By Stephan (stephancb) on 2024-06-10 08:51:57 in reply to 12 [link] [source]

Also other computing tools give here differing results:

Python 3.12.3 (main, Apr 17 2024, 00:00:00) [GCC 14.0.1 20240411 (Red Hat 14.0.1-0)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> round(0.15, 1)
0.1
julia> round(0.15; digits=1)
0.2
 < M A T L A B (R) >
>> round(0.15, 1)

ans =

    0.2000

It is quite unlikely that Matlab and Julia do here (slow) decimal arithmetic, both advertising themselves as high performance compute engines. Rather their binary implementations probably are "smart".

in Julia, for example, the round function has an optional third argument specifying which type of rounding should be performed. This would be an option also for SQLite, perhaps as a loadable extension. This seems preferable over another PRAGMA setting.

(16) By Richard Hipp (drh) on 2024-06-10 10:05:35 in reply to 1 [link] [source]

What should be the answer to the following two queries?

SELECT round(0.15, 1);
SELECT round(0.1499999999999999944488848768742172978818416595458984375, 1);

Should they give the same answer, or different answers? Should they both return 0.1, or 0.2, or should the first one return 0.2 and the second 0.1.

If you think they should return different answers, then that is a problem because the first arguments to round() in the cases above are just alternative spellings for exactly the same number. For an IEEE754 binary double, the following statement is true:

0.1499999999999999944488848768742172978818416595458984375 == 0.15

For that matter, these are also equivalent:

0.14999999999999999 == 0.15

In a IEEE754 binary double, all of 0.15, 0.14999999999999999, 0.1499999999999999944488848768742172978818416595458984375, and countless other decimal values are represented by the exact same bit pattern: 0x3fc3333333333333

The round() function has no way of knowing what decimal representation for the number the user originally typed in. The only information the round() function has is the binary IEEE754 double representation of that number, 0x3fc3333333333333. Yes, I could modify the round() function so that it renders round(0.15,1) as 0.2. But if I do so, then it would also render round(0.14999999999999999,1) as 0.2 because 0.15 and 0.14999999999999999 are the same number.

So what should round(0.15,1) return? Do you want the truth, or do you want the least surprising answer?

(17) By anonymous on 2024-06-10 10:30:45 in reply to 16 [link] [source]

I'm not anonymous from first post but joined this discusion. Yes not all developers knows how numbers are stored in IEEE-754 as you have described. But if you must use them and you must have round function that actually do round as demanded then solution exists.

I use this functions from pascal from John Herbster that are very fast and give me what I need.

https://github.com/vkorshun/vklib/blob/master/DecimalRounding_JH1.pas

If you would look at this you will see and no more explanation is needed.

(18) By Stephan (stephancb) on 2024-06-10 15:11:29 in reply to 16 [link] [source]

I confirm that in Python, Matlab, and Julia

python> 0.1499999999999999944488848768742172978818416595458984375==0.15
True

matlab> 0.1499999999999999944488848768742172978818416595458984375==0.15

ans =

  logical

   1
julia> 0.1499999999999999944488848768742172978818416595458984375==0.15
true

Nevertheless Pythons rounds 0.14999... to 0.1, but Matlab and Julia to 0.2 :

python> round(0.1499999999999999944488848768742172978818416595458984375, 1)
0.1

matlab>  round(0.1499999999999999944488848768742172978818416595458984375, 1) 

ans =

    0.2000
julia> round(0.1499999999999999944488848768742172978818416595458984375; digits=1)
0.2

(There are many more compute tools and database systems where this could be tested, these are just three where I have quick access)

While these tools seem to agree on the truth of the equality statement the results of the rounding differs. The "least surprising result" seems to have a "democratic" majority, but this would of course not be binding for SQLite.

(19.1) By Spindrift (spindrift) on 2024-06-10 15:36:02 edited from 19.0 in reply to 18 [link] [source]

Nevertheless Pythons rounds 0.14999... to 0.1, but Matlab and Julia to 0.2

Thanks for uncovering that Stephan.

Frankly, that's horrifying (for MATLAB and Julia).

One intuitively assumes that the "least surprising" result is due to the assumption that round() and similar are actually parsing the decimal representation.

But you seem to have shown that this MATLAB/Julia result is a fudge (from adding episilon to the floating point number first? I actually shudder just to think about it).

I'd prefer a "decimal" option to round.

But I'd hate a lying round that treats 0.14999999... as 0.2 with a passion.

(20) By Bo Lindbergh (_blgl_) on 2024-06-10 16:12:32 in reply to 19.1 [link] [source]

This is not necessarily evidence of fudging.

When using 64-bit binary floating point, 0.1499999999999999944488848768742172978818416595458984375*10.0 evaluates to 1.5 (exactly).

So you'd get the reported results if two-argument round is implemented as "scale by a power of ten; round to integer; scale by the inverse power of ten".

One way to test this might be to check what round(0.00015,4) returns. (0.00014999999999999998685946966947568625982967205345630645751953125*10000.0 evaluates to 1.4999999999999997779553950749686919152736663818359375 rather than 1.5.)

(23.1) By Stephan (stephancb) on 2024-06-11 04:21:33 edited from 23.0 in reply to 20 [source]

Yes, this is how Julia (which is open source) seems to have implemented round with a 2nd "digits" argument. While

julia> round(0.1499999999999999944488848768742172978818416595458984375; digits=1)
0.2
perhaps is "horrifying", we have
sqlite> select round(0.15*10)/10==round(0.15, 1);
0
which might be unsatisfactory as well. It looks that there is no clearcut answer.

IEEE 754 describes 4 methods for rounding floats to an integer, the default is "Round to nearest". IEEE 754 hardware would support this rounding to integer, and the scale-round-scale approach for rounding to significant digits would be very performant, in spirit closer to the IEEE 754 standard.

(24) By Harald Hanche-Olsen (hanche) on 2024-06-11 06:24:47 in reply to 23.1 [link] [source]

IEEE 754 describes 4 methods for rounding floats to an integer, the default is "Round to nearest". IEEE 754 hardware would support this rounding to integer, and the scale-round-scale approach for rounding to significant digits would be very performant, in spirit closer to the IEEE 754 standard.

According to wikipedia, there are two “round to nearest” methods: Ties to even, and ties away from zero. The default is supposedly round to even.

A tiny bit of experimentation reveals to my surprise that sqlite seems to do round away from zero:

sqlite> select round(2.5,0);
3.0
sqlite> select round(-2.5,0);
-3.0

Is that intentional, or is it merely due to the hardware I am running on? I get the same result on an M1 mac and a linux server running on Inten hardware.

(26) By Bo Lindbergh (_blgl_) on 2024-06-11 07:24:46 in reply to 24 [link] [source]

It might be a case of mimicking PostgreSQL, whose two-argument round is documented to round away from zero in case of ties.

(21) By anonymous on 2024-06-10 19:04:35 in reply to 1 [link] [source]

Not sure about your application, but if 2 decimals (cents?) you're after, then you may try a scale-round-scale approach.

sqlite> select round(0.15*10,0)/10;
0.2

sqlite> select round(0.14*10,0)/10;
0.1

(22) By Dave Mausner (dmausner) on 2024-06-10 20:09:46 in reply to 21 [link] [source]

sqlite> select round(0.15*10,0)/10; 0.2

Now that is very clever.

  1. Retains Sqlite tradition.
  2. Quickly "converts" N mantissa digits to integer.
  3. Quickly "converts" integer back to binary.
  4. Produces popular expectation.

Clear thinking!

(25) By Harald Hanche-Olsen (hanche) on 2024-06-11 06:34:38 in reply to 21 [link] [source]

Sorry to be such a curmudgeon, but barring a mathematical proof firmly based on the IEEE 754 standard and the method of converting a decimal representation of any number to a double, I don't believe this will always yield the expected result. I'd be quite surprised if it does.

(27) By Dave Mausner (dmausner) on 2024-06-11 14:32:45 in reply to 25 [link] [source]

Harald, don't you agree that integers have an exact IEEE 754 representation; and that ten times some value produces an integer portion which is ten times the integer portion of the value? Can you be more specific about when the algorithm will yield an unexpected result?

(28) By Bo Lindbergh (_blgl_) on 2024-06-11 16:23:44 in reply to 27 [link] [source]

Try rounding 0.575 to two digits.

(36.1) By Harald Hanche-Olsen (hanche) on 2024-06-12 11:44:58 edited from 36.0 in reply to 27 [link] [source]

Harald, don't you agree that integers have an exact IEEE 754 representation;

I do.

and that ten times some value produces an integer portion which is ten times the integer portion of the value?

Not at all, but I don't think you intended to say that. To be precise, the integer portion of 10*1.5 is 15, but ten times the integer portion of 1.5 is 10.

Can you be more specific about when the algorithm will yield an unexpected result?

Thanks to Bo Lindbergh in a sibling comment, I have an example: select round(0.575*100.0,0)/100.0; results in 0.57, where select round(57.5,0)/100.0; returns 0.58. Note that 57.5 has an exact representation as a double, whereas 0.575 does not.

(29) By Richard Hipp (drh) on 2024-06-11 17:18:33 in reply to 1 [link] [source]

For completeness sake, I did create a branch that contains a new algorithm for round(). The changes on that branch can be seen at https://sqlite.org/src/vdiff?from=56af06fa12104a1f&to=3dec4b35ecd68a4d.

Creating that branch does not imply any commitment to land the change at any point in the future. The branch might land someday. It it might be abandoned.

The new algorithm for rounding:

  1. If the next digit is 5-9, round up.

  2. If the next digit is 0-3, round down.

  3. If the next digit is 4 and the 4 is followed by at least three 9s and only 9s continue all the way out to the 16th significant digit, then round up.

  4. Else round down.

The algorithm difference is the addition of step 3. The change only appears in the round() function. A call to "format('%.1f',0.15)" or similar, continues to return "0.1".

(31) By anonymous on 2024-06-11 20:42:27 in reply to 29 [link] [source]

Please look at this: https://en.wikipedia.org/wiki/Rounding#:~:text=Rounding%20or%20rounding%20off%20means,expression%20%E2%88%9A2%20with%201.414.

Rounding half up is when digit is 5. You did not look at John Herbster solution. He's brilliant solution was fastest because he uses native round function. He has all possible options but for sqlite round half up would be enough. You did not comment on that and I think is not that bad you just ignore it.

(32) By Richard Hipp (drh) on 2024-06-12 00:42:09 in reply to 31 [link] [source]

Very well. A new branch uses the Herbster algorithm. https://sqlite.org/src/vdiff?from=6935ac71bad3d36c&to=552b1b106691eddb.

On the new branch we have:

  • round(0.1499999999999999944488848768742172978818416595458984375, 1) → 0.2

  • round(0.149999999999999966693309261245303787291049957275390625, 1) → 0.1

(33) By anonymous on 2024-06-12 02:00:09 in reply to 32 [link] [source]

(another annon here)

possible typo on line 468 func.c: double rSgn; /* Sign of the first first */

likely should be "first argument"?

(35) By anonymous on 2024-06-12 09:08:41 in reply to 32 [link] [source]

Thank you!

I predict no more questions about round function. Herbster algorithm is by far the best solution I have seen. It is fast and simple.

(30) By anonymous on 2024-06-11 18:26:50 in reply to 1 [link] [source]

Неre it is another floating point arithmetic test sample
3 queries with algebraically same 0.1+0.2-0.3 expression produce 3 different answers:

SQLite version 3.46.0 2024-05-23 13:25:27 (UTF-16 console I/O) Enter ".help" for usage hints. Connected to a transient in-memory database. Use ".open FILENAME" to reopen on a persistent database. sqlite> select 0.1+0.2-0.3; 5.55111512312578e-17 sqlite> select 0.1+0.2-0.2-0.1; 2.77555756156289e-17 sqlite> select 0.1-0.1+0.2-0.2; 0.0

(34.1) By Domingo (mingodad) on 2024-06-13 06:41:39 edited from 34.0 in reply to 30 [link] [source]

Interesting example and testing it with some other languages give this:
Lua/Python/Nodejs
====
print( 0.1+0.2-0.3);
print( 0.1+0.2-0.2-0.1);
print( 0.1-0.1+0.2-0.2);
====
console.log( 0.1+0.2-0.3);
console.log( 0.1+0.2-0.2-0.1);
console.log( 0.1-0.1+0.2-0.2);
====
Output:
====
5.551115123125783e-17
2.7755575615628914e-17
0
====
C/C++:
====
#include <stdio.h>
#include <stdlib.h>

void calc1(double a, double b, double c){printf("%g+%g-%g=%g\n", a,b,c, a+b-c);}
void calc2(double a, double b, double c, double d){printf("%g+%g-%g-%g=%g\n", a,b,c,d, a+b-c-d);}
void calc3(double a, double b, double c, double d){printf("%g-%g+%g-%g=%g\n", a,b,c,d, a-b+c-d);}

void scalc1(const char* sa, const char *sb, const char *sc){
	double a = atof(sa), b = atof(sb), c = atof(sc);
	printf("%g+%g-%g=%g\n", a,b,c, a+b-c);
}
void scalc2(const char* sa, const char *sb, const char *sc, const char *sd){
	double a = atof(sa), b = atof(sb), c = atof(sc), d = atof(sd);
	printf("%g+%g-%g-%g=%g\n", a,b,c,d, a+b-c-d);
}
void scalc3(const char* sa, const char *sb, const char *sc, const char *sd){
	double a = atof(sa), b = atof(sb), c = atof(sc), d = atof(sd);
	printf("%g-%g+%g-%g=%g\n", a,b,c,d, a-b+c-d);
}

int main(int argc, char *argv[])
{
  const char *ca = (argc > 1) ? argv[1] : "0.1";
	const char *cb = (argc > 2) ? argv[2] : "0.2";
	const char *cc = (argc > 3) ? argv[3] : "0.3";
	double sa = atof(ca), sb = atof(cb), sc = atof(cc);
	double a = 0.1, b = 0.2, c = 0.3;
	printf("%g, %d : %g, %d : %g, %d\n", a, a == sa, b, b == sb, c, c == sc);
	calc1(a,b,c);
	calc1(sa,sb,sc);
	scalc1(ca,cb,cc);
	calc2(a,b,b,a);
	calc2(sa,sb,sb,sa);
	scalc2(ca,cb,cb,ca);
	calc3(a,a,b,b);
	calc3(sa,sa,sb,sb);
	scalc3(ca,ca,cb,cb);
	printf("%g\n", 0.1+0.2-0.3);
	printf("%g\n", 0.1+0.2-0.2-0.1);
	printf("%g\n", 0.1-0.1+0.2-0.2);
	return 0;
}
====
Output:
====
./test-fp-sum
0.1, 1 : 0.2, 1 : 0.3, 1
0.1+0.2-0.3=5.55112e-17
0.1+0.2-0.3=5.55112e-17
0.1+0.2-0.3=5.55112e-17
0.1+0.2-0.2-0.1=2.77556e-17
0.1+0.2-0.2-0.1=2.77556e-17
0.1+0.2-0.2-0.1=2.77556e-17
0.1-0.1+0.2-0.2=0
0.1-0.1+0.2-0.2=0
0.1-0.1+0.2-0.2=0
5.55112e-17
2.77556e-17
0
====

Edited the C/C++ example and use "%g" instead of "%f" because lake of precision was showing a different result.

(38) By AlexJ (CompuRoot) on 2024-06-12 14:22:17 in reply to 34.0 [link] [source]

LibreOffice Calc - is still trustful on this math, it calculated expressions

0.1+0.2-0.3 = 0.0
0.1+0.2-0.2-0.1 = 0.0
0.1-0.1+0.2-0.2 = 0.0
as expected, with precision set up to 20 (maximal) decimal digits

(37.1) By Richard Hipp (drh) on 2024-06-12 13:36:52 edited from 37.0 in reply to 1 [link] [source]

In the diagram below, the vertical line represents the real axis, increasing upwards. IEEE754 binary64 values (hereafter "double values") represent a quantization of the real axis. In the diagram, the red dots are five double values in the vicinity of 0.15. Each double value is marked with both its hex value and the exact decimal meaning of that value. The exact position of the real value 0.15 is also marked.

One can think of each discrete double value not as an exact real number, but rather as a range of real numbers. The angled lines in the diagram attempt to show the range of real numbers that each double value represents.

Here are two of many possible rounding algorithms:

  1. Round up if the exact value of the double (the red dot) is at or above the inflection point.

  2. Round up if the inflection point is anywhere within the range of real numbers that the double represents.

SQLite version 3.46.0 and earlier implement algorithm 1. The OP for this thread thinks that the algorithm should be 2. Algorithm 2 is implemented by the round-up-2 branch.

0.15 0x3fc3333333333335 0.1500000000000000499600361081320443190634250640869140625 0x3fc3333333333334 0.15000000000000002220446049250313080847263336181640625 0x3fc3333333333333 0.1499999999999999944488848768742172978818416595458984375 0x3fc3333333333332 0.149999999999999966693309261245303787291049957275390625 0x3fc3333333333331 0.1499999999999999389377336456163902767002582550048828125
arrow from 1,0.3 to 1,5.8 thick
circle at 1,1 fill red width 0.08
circle at 1,2 fill red width 0.08
circle at 1,3 fill red width 0.08
circle at 1,4 fill red width 0.08
circle at 1,5 fill red width 0.08
arrow from 0.7,3.2 to 0.9,3.2 thin
text "0.15" with e at 0.65,3.2
line from 1.1,0.51 to 1.3,1 then to 1.1,1.49 thin
line from 1.1,1.51 to 1.3,2 then to 1.1,2.49 thin
line from 1.1,2.51 to 1.3,3 then to 1.1,3.49 thin
line from 1.1,3.51 to 1.3,4 then to 1.1,4.49 thin
line from 1.1,4.51 to 1.3,5 then to 1.1,5.49 thin
box "0x3fc3333333333335" ljust "0.1500000000000000499600361081320443190634250640869140625" ljust fit with w at 1.3,5 invis
box "0x3fc3333333333334" ljust "0.15000000000000002220446049250313080847263336181640625" ljust fit with w at 1.3,4 invis
box "0x3fc3333333333333" ljust "0.1499999999999999944488848768742172978818416595458984375" ljust fit with w at 1.3,3 invis
box "0x3fc3333333333332" ljust "0.149999999999999966693309261245303787291049957275390625" ljust fit with w at 1.3,2 invis
box "0x3fc3333333333331" ljust "0.1499999999999999389377336456163902767002582550048828125" ljust fit with w at 1.3,1 invis