-
Notifications
You must be signed in to change notification settings - Fork 1.1k
JavaScript & Oracle Number handling: two connections with differents fetchAsString parameters #665
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
you can set those up on every connection.execute call |
@atiertant See the fetchInfo section of the doc. |
@sagiegurari connection.execute is asynchronous, are you sure this is working if there are many request at the same time? @dmcghan this is for explicit column not for every one like fetchAsString |
@atiertant That's true, there's currently no generic solution at the execute level. Could you please explain the use case? Why does fetchAsString not work for you? |
@dmcghan first problem was node-oracledb don't bind float correctly, to avoid this problem, we used fetchAsString but for integrate this in knex we need to write unit test, the fetchAsString test is working, but other test fail because they sould not return string... |
Did you log an issue? Do you have a test case? It sounds like this is the real problem you're having so I think it's best we look at that. |
The attribute hierarchy change request is tracked in #361 At a guess, any number 'issue' is an expected artifact of conversion to/from different binary representations. |
@cjbj |
@bjouhier java and many other languages have same precision in operations: see https://github.com/MikeMcl/bignumber.js/ there are solutions ... |
Of course but JavaScript only has one number datatype, and it's an IEEE 754 floating point. |
@bjouhier problem is not in javascript Number datatype (this is a 64bits signed number)
only mathematical operations loss precision with floating point but you can perform them with integer and divide result after... |
@atiertant Sorry to contradict you but the problem is in the JavaScript Number datatype. This datatype is an IEEE 754 floating point. See https://www.ecma-international.org/ecma-262/7.0/index.html section 4.3.21 When you write |
@bjouhier your right, it's not exactly |
If you want to do precise arithmetics with decimals you should not use JavaScript numbers. Instead you should use a big decimal library like the one you mentioned above. But you should manipulate the values only as instances of the When reading or writing to Oracle, you should pass the values as strings, not numbers. Oracle numbers can have up to 38 decimals of precision (and, unlike JS numbers, they represent these numbers exactly). A 64 bit integer has 19.27 decimals of precision but a JS number only has 15.95 decimals of precision. So you're heading for trouble it you try to map high precision database values to JS values, even integer values that would fit into a 64 bit int. Try |
@atiertant The problem is the JavaScript Number type. The stringified value is sometimes rounded. We can't depend on that. A JavaScript decimal type recently came up in a twitter discussion from one of the tc39 folks: If JavaScript gets a decimal type, then we can look at using that instead. |
@dmcghan if the problem was the JavaScript Number type why must i use a fetchAsString then parsefloat that return a JavaScript Number ???
maybe the max oracle database precision could not be handled with JavaScript Number type but at now node-oracledb do worst than javascript can... |
Don't convert to JS number (don't use |
@atiertant fetchAsString with parseFloat is not fixing the problem, it's masking it. If you tried to do arithmetic with the result this would become more obvious. let x = parseFloat('.1');
let y = parseFloat('.2');
console.log(x); // 0.1
console.log(y); // 0.2
console.log(x + y); // 0.30000000000000004 We can't depend on rounding display values, we need a decimal type. In the meantime, decimal arithmetic should be done in Oracle and the results brought through as strings to be displayed. As for your library, I'd recommend not trying to fix this on your own. |
You are fooling yourself into thinking that JavaScript can do a good job with decimal numbers. JS numbers only represent a very small subset of decimal numbers exactly. Even numbers as simple as 0.1 or 0.2 are only approximations (but 0.125 is exact!). If you want to do decimal arithmetics, stay away from JS numbers and use a big decimal library. The problem has nothing to do with the oracledb driver. It has to do with JavaScript itself. |
@dmcghan i use mathjs for mathematical operation. but javascript can handle stringify number correctly, and using them with mathjs (stringifing them and using bigNumber internaly) but those caming from node-oracledb not, because the stringified value is wrong! if you used integer value in your conversion algorithm and divide it at end, you should have a correct string value. |
@bjouhier wouldn't like to do decimal arithmetics, just have a correct value on assignment, this is a fact: number caming from database could be assigned correctly because parseFloat do, sure there are some case where it could not work but 2 digits ... |
@atiertant Why don't you use For the fun of it I tried to find out how many numbers in the 0.000000, 0.000001, 0.000002, 0.000003, ... 0.999999 series are exact in JavaScript. It's only 64 out of 1,000,000! With 2 digits, it is only 4 out of 100: 0.00, 0.25, 0.50 and 0.75. All the others numbers are approximations. |
@atiertant To understand your problem better I have a few questions:
|
hope this to be more clear:
as you can see string value is wrong...but javascript can assign this value |
I tried some variants and dived a bit into the source of the driver. Interestingly, I did not see any special parsing in the oracledb driver source code. It just transfers the values between OCI and V8 (https://github.com/oracle/node-oracledb/blob/master/src/njs/src/njsConnection.cpp#L4047 for example). The conversion between the oracle number and double must be inside OCI and may differ from what a you get by first converting to string, and then parsing with It is very likely that you will get the same discrepancy if you call OCI from C and retrieve Oracle decimal numbers into C Morale: to get reliable results with decimal values, always fetch them as strings. |
so this is an oracle(maybe in OCI) bug
so, to get reliable results, do not use oracle methods, rewrite your own... |
Not quite. OCI has a proper API to retrieve numbers with full precision: https://docs.oracle.com/cd/B12037_01/appdev.101/b10779/oci11oty.htm#434061. The problem is that JavaScript does not have any built-in type for decimals, just a floating-point number type. A .NET driver, for example, would be able to do exact conversions to .NET decimals. An alternative would be to expose the OCI Number class to JavaScript. I know this has been suggested but I don't think it has been implemented so far. |
@atiertant I understand your issue. You don't like the fact that reading decimals as numbers sometimes gives values that are a bit off. But this is a well known issue; this is covered by the documentation and there is an option ( I tried to help you and dig to give you an explanation where this could come from (I'm curious too). I cannot do much more. |
@bjouhier the only thing i don't like is saying "The problem is the JavaScript Number type" in this case, this is wrong! so oracle could reconize it's a bug in their code, they fix or not but stop saying this is not possible in javascript...
this doesn't solve the problem, this is a trick to avoid the problem.(but haven't got choice) that said ! for now, i need |
That's Oracle's call, not mine. I don't work for Oracle. |
From the doc:
Maybe this can help you handle multiple connections. This is how we handle this issue. Otherwise, I'm pretty sure that the |
As stated many times already, the problem goes to the fact that decimal numbers can only be approximated in floating point. Arguing about which approximation is "correct" seems pointless since neither of them are accurate. Both are approximations! The only advantage that the JavaScript approximation has over the Oracle approximation is that it is consistent with itself. If, however, you attempt to perform any arithmetic with those floating point numbers the inaccuracy will become obvious soon enough -- again, as has been shown above. The real solution is to use decimal numbers in JavaScript as well -- and there are a number of third party libraries that support them. But in order to use those you have to use fetchAsString and then iterate over the rows returned to transform those strings into the decimal numbers -- which incurs a great deal of overhead and is disruptive to the code. One possible solution to this problem that I have considered is the introduction of a "hook" that could be specified at various levels (globally, per connection, per bind variable). This hook would be a callable that would accept one argument (the value that would ordinarily be returned by node-oracledb) and would return the value that should be returned instead. This would allow the use of any decimal number library (or even parseFloat() for those who prefer it!) without having to massage the output from the node-oracledb library. It could of course be used for other purposes! Does this sound like a good idea? Like it might resolve your issue? Thoughts welcome! |
I would add one more advantage, which is @atiertant's main use case and a common one for Node.js in general: REST APIs that output JSON. In this case, you're not planning on doing arithmetic, you're just sending the data along for another system to leverage. Sending the number out as a string is often undesirable (though is still needed for really large numbers).
To better illustrate this concept (though the final API could be different) here's an example: oracledb.fetchMappings = [
{
from: oracledb.NUMBER,
to: oracledb.STRING,
converter: parseFloat
}
] |
Right. JavaScript uses the IEEE 754-2008 “round to nearest, ties to even” rounding mode. Libraries written in other languages (like OCI) may use a different rounding rule when they convert decimals to floating-point. This cannot be qualified as an OCI bug because OCI may be called from different languages which may have different rules (or no rule at all, leaving this up to the compiler!). The oracledb driver could bypass the decimal-to-double conversion done by OCI, to align strictly on the JavaScript rule (easy, just call @anthony-tuininga The current API does the job for us but I can see the benefit of having a hook. What seems to be lacking most is a way to configure the mapping at the connection or pool level. |
Thanks for the feedback. I agree that being able to configure the mapping at a connection or pool level would be a good thing to have. What sort of behaviour would you like to see? One possibility would be to extend fetchInfo and permit the column name to be a data type as well, as in this:
And if the hook was implemented it could look like this:
This format could then be used globally, at the connection level or at the execute level. At the connection level, if the attribute is not set by the user, the global value would be used. Does that seem reasonable to you? |
@anthony-tuininga API looks reasonable except for the mix of keys (column names and datatype constants) under |
@bjouhier Thanks. We'll keep that in mind. |
@anthony-tuininga One potential issue with hooks might be performance: every call to the hook will have to go through the C++/JavaScript boundary. This may be more costly than returning all the values as strings and transforming the result's |
@bjouhier Good point. We'll need to test that. Thanks. |
@anthony-tuininga don't confuse binary decimal value used for arithmetic and string returned by
there is no advantage to JavaScript approximation or an other but this is a JavaScript driver ! this is an OCI bug or a node-oracledb buggy use of it because C++ double value should be exact and then nan will handle convertion correctly for node.js about hook, i would prefer the |
|
@atiertant One clarification: I don't completely disagree with you. You have a point: the oracle JS driver could do a better job at converting oracle decimals to JS numbers. But there is a cost and it would not solve all the problems that The problem is that your phrasing (.toString is not [an approximation], C++ double value should be exact, there is no advantage to JavaScript approximation, ...) is too approximative (when not wrong) and complicates the discussion. A good article: http://blog.reverberate.org/2016/02/06/floating-point-demystified-part2.html. It explains the advantage of the JavaScript rounding rule (in comparison to Lua). |
@bjouhier one more time you're right ! but ...
am i right ? |
Yes, |
@bjouhier from 2 digits not working now on node-oracledb to 15.95 handled by JS numbers, driver could do better. now questions are: |
The double comes from OCI, which is not open source. No hope to change it (OCI is used in zillions of 3rd party code and I doubt that Oracle wants to disrupt this eco-system - we are part of it and wouldn't want this). But the problem can be fixed in the oracledb driver. An easy way is to ask OCI to return all decimal values as strings (as if If you are confortable with C++ you can attempt a PR but it would be better to discuss it with Oracle staff first and you would probably need to control this with a flag, for backward compat. |
|
The problem is not one of precision but of rounding mode. As I understand it OCI does not let you control the rounding mode so you have to pass the value as string. @anthony-tuininga's API proposal would solve your problem:
So maybe you should push for this API to move forwards. It is more general/powerful. |
@bjouhier more general for sure but slower... if precision was set to 17 digits, rounding mode doesn't matter no? |
Increasing the precision won't help. What you have to do is reproduce exactly the rounding rule used by $ node
> parseFloat('0.9999999999999999444')
0.9999999999999999
> parseFloat('0.9999999999999999445')
1 Why does the result change between That's because JS imposes a very special rounding rule (one that roundtrips) and it does not help to have more precision on the input. |
Closing - I think this has run its course. |
Hello, |
There is no way, currently. If you would like to see a way developed, please create a new enhancement request detailing what you would like to see. Thanks! |
how to create two connections: first with fetchAsString on numbers and second without any ?
The text was updated successfully, but these errors were encountered: