-
Notifications
You must be signed in to change notification settings - Fork 579
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
util-dynamodb
should handle (and round-trip) largish floating point numbers
#6571
Comments
Hey @alex-at-cascade , I can reproduce this issue. There's 2 issues I found 1 - The error messages you posted in this github issue. V2 2 - The value is not correct if I use BigInt to fix the error in V3 const marshalled_V2 = AWS.DynamoDB.Converter.marshall({foo: 1.23e+40});
console.log("Marshalled_v2:", JSON.stringify(marshalled_V2, null, 2));
//V3
// Marshall a JavaScript object to DynamoDB format
const marshalled = marshall({ foo: BigInt(1.23e+40) });
console.log("Marshalled:", JSON.stringify(marshalled, null, 2)); The result I have
1.23e+40 should be 1.23× 10^40, but the marshalled value is 12299999999999999515319483038827796234240. I will report this issue to the SDK dev team and I will keep you updated! Thanks! |
All numbers exceeding the absolute value of MAX_SAFE_INTEGER should be wrapped in See https://www.npmjs.com/package/@aws-sdk/lib-dynamodb with entry Large Numbers and NumberValue. import { DynamoDB } from "@aws-sdk/client-dynamodb";
import { NumberValue, DynamoDBDocument } from "@aws-sdk/lib-dynamodb";
// Note, the client will not validate the acceptability of the number
// in terms of size or format.
// It is only here to preserve your precise representation.
const client = DynamoDBDocument.from(new DynamoDB({}));
await client.put({
Item: {
id: 1,
smallNumber: NumberValue.from("123"),
bigNumber: NumberValue.from("1000000000000000000000.000000000001"),
nSet: new Set([123, NumberValue.from("456"), 789]),
},
});
|
Seems like |
Also worth noting that one would normally expect |
Hey @alex-at-cascade, Thanks for the feedback! I should make it more clear - The root causes of the precision issue and the magnitude issue are the same, which is we are not using As @kuhe replied earlier,
Please use |
A number like |
I don't consider 1.23+40 to be fine in JS, since it is represented as 1.22999999999999995153194830388E40, despite its intact round trip. For large numbers, even though certain numbers can be represented exactly in Float64, they cannot be disambiguated from their neighboring values, so all of them are considered imprecise beyond the safe integer thresholds. Here is how to handle it in the current version of the SDK, and below a feature we can implement to support ignoring precision: const { unmarshall, marshall } = require("@aws-sdk/util-dynamodb");
const { NumberValue } = require("@aws-sdk/lib-dynamodb");
const impreciseNumber = 1.23e40;
const m = marshall({ foo: NumberValue.from(String(impreciseNumber)) }, {});
console.log("marshalled", m);
const u = unmarshall({ foo: { N: "1.23e+40" } }, { wrapNumbers: true });
console.log("unmarshalled", u);
function convertNumberValue(obj, mapper) {
if (typeof obj !== "object" || !obj) {
return obj;
}
for (const [key, val] of Object.entries(obj)) {
if (val instanceof NumberValue) {
obj[key] = mapper(obj[key]);
} else {
obj[key] = convertNumberValue(obj[key], mapper);
}
}
return obj;
}
console.log("converted to JS number", convertNumberValue(u, Number)); Output:
We don't want people to unsuspectingly use imprecise numbers, so I suggest we create the following opt-in features: const impreciseNumber = 1.23e40;
// opt-in boolean to not throw error on imprecise number.
const m = marshall({ foo: impreciseNumber }, { allowImpreciseNumbers: true });
// allow a function to be used in the wrapNumbers option.
// If you specify `Number`, then even large numeric values will be converted to the basic number type.
const u = unmarshall({ foo: { N: "1.23e+40" } }, { wrapNumbers: Number }); |
As long as there's a way (via an option) to round-trip in javascript with a minimum of fuss (which will by far be the most common use of this library), it will be usable at least. (I do understand that, in the unusual case of higher precision numbers generated from some other source/language, there could be loss of precision on conversion - but even then, checking against |
One could also consider, when unmarshalling, given |
Ran has contributed this feature, as of https://www.npmjs.com/package/@aws-sdk/lib-dynamodb/v/3.689.0. The syntax to use import { DynamoDB } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb";
const ddb = new DynamoDB();
const doc = DynamoDBDocument.from(ddb, {
marshallOptions: {
allowImpreciseNumbers: true,
},
unmarshallOptions: {
wrapNumbers: Number,
},
});
await doc.put({
TableName: "test",
Item: {
id: "imprecise-number",
data: 1.23e40,
},
});
const get = await doc.get({
TableName: "test",
Key: {
id: "imprecise-number",
},
});
console.log("get", get);
|
Checkboxes for prior research
Describe the bug
After attempting to upgrade some code to the V3 SDK, we encountered the following problems. Both of the following now throw an error:
marshall({ foo: 1.23e+40 })
: "Error: Number 1.23e+40 is greater than Number.MAX_SAFE_INTEGER. Use BigInt."unmarshall({ foo: { N: "1.23e+40" } })
: "Error: 1.23e+40 can't be converted to BigInt. Set options.wrapNumbers to get string value."Neither of these cases should be an error, since they are well within the range of what DynamoDB supports for numbers. And both work exactly as expected with the old V2 Converter functions. (In addition, the suggestion to use BigInt for floating point values is bizarre. Related to this is that numbers lacking a decimal point but greater than MAX_SAFE_INTEGER do strangely get converted to BigInt instead of Number when unmarshalled, which then triggers an error when the result is passed into
JSON.stringify
.)Regression Issue
SDK version number
"@aws-sdk/util-dynamodb": "^3.670.0"
Which JavaScript Runtime is this issue in?
Node.js
Details of the browser/Node.js/ReactNative version
v18.20.0
Reproduction Steps
Observed Behavior
In the first case: "Error: Number 1.23e+40 is greater than Number.MAX_SAFE_INTEGER. Use BigInt."
In the second case: "Error: 1.23e+40 can't be converted to BigInt. Set options.wrapNumbers to get string value."
Expected Behavior
Same as the V2 Converter, namely:
{foo: {N: "1.23e+40"}}
{foo: 1.23e+40}
Possible Solution
As this is a JS SDK, for numerical values, the converters should perform direct Number operations following typical JS conventions. (Any astonishing type conversions should be eliminated, unless specific options are provided.)
Additional Information/Context
No response
The text was updated successfully, but these errors were encountered: