Skip to content

Commit 722dd1d

Browse files
committed
decimal conversion
1 parent 6123a67 commit 722dd1d

File tree

2 files changed

+184
-36
lines changed

2 files changed

+184
-36
lines changed

bindings/src/duckdb_node_bindings.cpp

Lines changed: 74 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,65 @@ duckdb_timestamp_struct GetTimestampPartsFromObject(Napi::Object timestamp_parts
116116
return { date, time };
117117
}
118118

119+
duckdb_hugeint GetHugeIntFromBigInt(Napi::Env env, Napi::BigInt bigint) {
120+
int sign_bit;
121+
size_t word_count = 2;
122+
uint64_t words[2];
123+
bigint.ToWords(&sign_bit, &word_count, words);
124+
if (word_count > 2) {
125+
throw Napi::Error::New(env, "bigint out of hugeint range");
126+
}
127+
uint64_t lower = word_count > 0 ? (sign_bit ? -1 : 1) * words[0] : 0;
128+
int64_t upper = word_count > 1 ? (sign_bit ? -1 : 1) * words[1] : (word_count > 0 && sign_bit ? -1 : 0);
129+
return { lower, upper };
130+
}
131+
132+
Napi::BigInt MakeBigIntFromHugeInt(Napi::Env env, duckdb_hugeint hugeint) {
133+
int sign_bit = hugeint.upper < 0 ? 1 : 0;
134+
size_t word_count = hugeint.upper == -1 ? 1 : 2;
135+
uint64_t words[2];
136+
words[0] = (sign_bit ? -1 : 1) * hugeint.lower;
137+
words[1] = (sign_bit ? -1 : 1) * hugeint.upper;
138+
return Napi::BigInt::New(env, sign_bit, word_count, words);
139+
}
140+
141+
duckdb_uhugeint GetUHugeIntFromBigInt(Napi::Env env, Napi::BigInt bigint) {
142+
int sign_bit;
143+
size_t word_count = 2;
144+
uint64_t words[2];
145+
bigint.ToWords(&sign_bit, &word_count, words);
146+
if (word_count > 2 || sign_bit) {
147+
throw Napi::Error::New(env, "bigint out of uhugeint range");
148+
}
149+
uint64_t lower = word_count > 0 ? words[0] : 0;
150+
uint64_t upper = word_count > 1 ? words[1] : 0;
151+
return { lower, upper };
152+
}
153+
154+
Napi::BigInt MakeBigIntFromUHugeInt(Napi::Env env, duckdb_uhugeint uhugeint) {
155+
int sign_bit = 0;
156+
size_t word_count = 2;
157+
uint64_t words[2];
158+
words[0] = uhugeint.lower;
159+
words[1] = uhugeint.upper;
160+
return Napi::BigInt::New(env, sign_bit, word_count, words);
161+
}
162+
163+
Napi::Object MakeDecimalObject(Napi::Env env, duckdb_decimal decimal) {
164+
auto decimal_obj = Napi::Object::New(env);
165+
decimal_obj.Set("width", Napi::Number::New(env, decimal.width));
166+
decimal_obj.Set("scale", Napi::Number::New(env, decimal.scale));
167+
decimal_obj.Set("value", MakeBigIntFromHugeInt(env, decimal.value));
168+
return decimal_obj;
169+
}
170+
171+
duckdb_decimal GetDecimalFromObject(Napi::Env env, Napi::Object decimal_obj) {
172+
uint8_t width = decimal_obj.Get("width").As<Napi::Number>().Uint32Value();
173+
uint8_t scale = decimal_obj.Get("scale").As<Napi::Number>().Uint32Value();
174+
auto value = GetHugeIntFromBigInt(env, decimal_obj.Get("value").As<Napi::BigInt>());
175+
return { width, scale, value };
176+
}
177+
119178
// Externals
120179

121180
template<typename T>
@@ -1230,17 +1289,8 @@ class DuckDBNodeAddon : public Napi::Addon<DuckDBNodeAddon> {
12301289
// function hugeint_to_double(hugeint: bigint): number
12311290
Napi::Value hugeint_to_double(const Napi::CallbackInfo& info) {
12321291
auto env = info.Env();
1233-
auto hugeint_as_bigint = info[0].As<Napi::BigInt>();
1234-
int sign_bit;
1235-
size_t word_count = 2;
1236-
uint64_t words[2];
1237-
hugeint_as_bigint.ToWords(&sign_bit, &word_count, words);
1238-
if (word_count > 2) {
1239-
throw Napi::Error::New(env, "bigint out of hugeint range");
1240-
}
1241-
uint64_t lower = word_count > 0 ? (sign_bit ? -1 : 1) * words[0] : 0;
1242-
int64_t upper = word_count > 1 ? (sign_bit ? -1 : 1) * words[1] : (word_count > 0 && sign_bit ? -1 : 0);
1243-
duckdb_hugeint hugeint = { lower, upper };
1292+
auto bigint = info[0].As<Napi::BigInt>();
1293+
auto hugeint = GetHugeIntFromBigInt(env, bigint);
12441294
auto output_double = duckdb_hugeint_to_double(hugeint);
12451295
return Napi::Number::New(env, output_double);
12461296
}
@@ -1251,29 +1301,15 @@ class DuckDBNodeAddon : public Napi::Addon<DuckDBNodeAddon> {
12511301
auto env = info.Env();
12521302
auto input_double = info[0].As<Napi::Number>().DoubleValue();
12531303
auto hugeint = duckdb_double_to_hugeint(input_double);
1254-
int sign_bit = input_double < 0 ? 1 : 0;
1255-
size_t word_count = hugeint.upper == -1 ? 1 : 2;
1256-
uint64_t words[2];
1257-
words[0] = (sign_bit ? -1 : 1) * hugeint.lower;
1258-
words[1] = (sign_bit ? -1 : 1) * hugeint.upper;
1259-
return Napi::BigInt::New(env, sign_bit, word_count, words);
1304+
return MakeBigIntFromHugeInt(env, hugeint);
12601305
}
12611306

12621307
// DUCKDB_API double duckdb_uhugeint_to_double(duckdb_uhugeint val);
12631308
// function uhugeint_to_double(uhugeint: bigint): number
12641309
Napi::Value uhugeint_to_double(const Napi::CallbackInfo& info) {
12651310
auto env = info.Env();
1266-
auto uhugeint_as_bigint = info[0].As<Napi::BigInt>();
1267-
int sign_bit;
1268-
size_t word_count = 2;
1269-
uint64_t words[2];
1270-
uhugeint_as_bigint.ToWords(&sign_bit, &word_count, words);
1271-
if (word_count > 2 || sign_bit) {
1272-
throw Napi::Error::New(env, "bigint out of uhugeint range");
1273-
}
1274-
uint64_t lower = word_count > 0 ? words[0] : 0;
1275-
uint64_t upper = word_count > 1 ? words[1] : 0;
1276-
duckdb_uhugeint uhugeint = { lower, upper };
1311+
auto bigint = info[0].As<Napi::BigInt>();
1312+
auto uhugeint = GetUHugeIntFromBigInt(env, bigint);
12771313
auto output_double = duckdb_uhugeint_to_double(uhugeint);
12781314
return Napi::Number::New(env, output_double);
12791315
}
@@ -1284,26 +1320,28 @@ class DuckDBNodeAddon : public Napi::Addon<DuckDBNodeAddon> {
12841320
auto env = info.Env();
12851321
auto input_double = info[0].As<Napi::Number>().DoubleValue();
12861322
auto uhugeint = duckdb_double_to_uhugeint(input_double);
1287-
int sign_bit = 0;
1288-
size_t word_count = 2;
1289-
uint64_t words[2];
1290-
words[0] = uhugeint.lower;
1291-
words[1] = uhugeint.upper;
1292-
return Napi::BigInt::New(env, sign_bit, word_count, words);
1323+
return MakeBigIntFromUHugeInt(env, uhugeint);
12931324
}
12941325

12951326
// DUCKDB_API duckdb_decimal duckdb_double_to_decimal(double val, uint8_t width, uint8_t scale);
12961327
// function double_to_decimal(double: number, width: number, scale: number): Decimal
12971328
Napi::Value double_to_decimal(const Napi::CallbackInfo& info) {
12981329
auto env = info.Env();
1299-
throw Napi::Error::New(env, "Not implemented yet");
1330+
auto input_double = info[0].As<Napi::Number>().DoubleValue();
1331+
auto width = info[1].As<Napi::Number>().Uint32Value();
1332+
auto scale = info[2].As<Napi::Number>().Uint32Value();
1333+
auto decimal = duckdb_double_to_decimal(input_double, width, scale);
1334+
return MakeDecimalObject(env, decimal);
13001335
}
13011336

13021337
// DUCKDB_API double duckdb_decimal_to_double(duckdb_decimal val);
13031338
// function decimal_to_double(decimal: Decimal): number
13041339
Napi::Value decimal_to_double(const Napi::CallbackInfo& info) {
13051340
auto env = info.Env();
1306-
throw Napi::Error::New(env, "Not implemented yet");
1341+
auto decimal_obj = info[0].As<Napi::Object>();
1342+
auto decimal = GetDecimalFromObject(env, decimal_obj);
1343+
auto output_double = duckdb_decimal_to_double(decimal);
1344+
return Napi::Number::New(env, output_double);
13071345
}
13081346

13091347
// DUCKDB_API duckdb_state duckdb_prepare(duckdb_connection connection, const char *query, duckdb_prepared_statement *out_prepared_statement);

bindings/test/conversion.test.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,12 @@ suite('conversion', () => {
247247
test('near max', () => {
248248
expect(duckdb.double_to_hugeint(1.7014118346046922e+38)).toBe(2n ** 127n - 2n ** 74n);
249249
});
250+
test('out of range (positive)', () => {
251+
expect(duckdb.double_to_hugeint(1.8e+38)).toBe(0n);
252+
});
253+
test('out of range (negative)', () => {
254+
expect(duckdb.double_to_hugeint(-1.8e+38)).toBe(0n);
255+
});
250256
});
251257
suite('uhugeint_to_double', () => {
252258
test('zero', () => {
@@ -290,5 +296,109 @@ suite('conversion', () => {
290296
test('near max', () => {
291297
expect(duckdb.double_to_uhugeint(1.7014118346046922e+38)).toBe(2n ** 127n - 2n ** 74n);
292298
});
299+
test('out of range (positive)', () => {
300+
expect(duckdb.double_to_uhugeint(3.5e+38)).toBe(0n);
301+
});
302+
test('out of range (negative)', () => {
303+
expect(duckdb.double_to_uhugeint(-1)).toBe(0n);
304+
});
305+
});
306+
suite('double_to_decimal', () => {
307+
test('zero', () => {
308+
expect(duckdb.double_to_decimal(0, 4, 1)).toStrictEqual({ width: 4, scale: 1, value: 0n });
309+
});
310+
test('one', () => {
311+
expect(duckdb.double_to_decimal(1, 9, 4)).toStrictEqual({ width: 9, scale: 4, value: 10000n });
312+
});
313+
test('negative one', () => {
314+
expect(duckdb.double_to_decimal(-1, 9, 4)).toStrictEqual({ width: 9, scale: 4, value: -10000n });
315+
});
316+
test('one word', () => {
317+
expect(duckdb.double_to_decimal(123456789012.34568, 18, 6)).toStrictEqual(
318+
{ width: 18, scale: 6, value: 123456789012345680n }
319+
);
320+
});
321+
test('two words', () => {
322+
expect(duckdb.double_to_decimal(1.2345678901234567e+27, 38, 10)).toStrictEqual(
323+
{ width: 38, scale: 10, value: 12345678901234567525491324606797053952n }
324+
);
325+
});
326+
test('negative one word', () => {
327+
expect(duckdb.double_to_decimal(-123456789012.34568, 18, 6)).toStrictEqual(
328+
{ width: 18, scale: 6, value: -123456789012345680n }
329+
);
330+
});
331+
test('negative two words', () => {
332+
expect(duckdb.double_to_decimal(-1.2345678901234567e+27, 38, 10)).toStrictEqual(
333+
{ width: 38, scale: 10, value: -12345678901234567525491324606797053952n }
334+
);
335+
});
336+
test('out of range (positive)', () => {
337+
expect(duckdb.double_to_decimal(1e+38, 38, 0)).toStrictEqual(
338+
{ width: 0, scale: 0, value: 0n }
339+
);
340+
});
341+
test('out of range (negative)', () => {
342+
expect(duckdb.double_to_decimal(-1e+38, 38, 0)).toStrictEqual(
343+
{ width: 0, scale: 0, value: 0n }
344+
);
345+
});
346+
test('out of range (width)', () => {
347+
expect(duckdb.double_to_decimal(1, 39, 0)).toStrictEqual(
348+
{ width: 0, scale: 0, value: 0n }
349+
);
350+
});
351+
test('out of range (scale)', () => {
352+
expect(duckdb.double_to_decimal(1, 4, 4)).toStrictEqual(
353+
{ width: 0, scale: 0, value: 0n }
354+
);
355+
});
356+
});
357+
suite('decimal_to_double', () => {
358+
test('zero', () => {
359+
expect(duckdb.decimal_to_double({ width: 4, scale: 1, value: 0n })).toBe(0);
360+
});
361+
test('one', () => {
362+
expect(duckdb.decimal_to_double({ width: 9, scale: 4, value: 10000n })).toBe(1);
363+
});
364+
test('negative one', () => {
365+
expect(duckdb.decimal_to_double({ width: 9, scale: 4, value: -10000n })).toBe(-1);
366+
});
367+
test('one word', () => {
368+
expect(
369+
duckdb.decimal_to_double({
370+
width: 18,
371+
scale: 6,
372+
value: 123456789012345680n,
373+
})
374+
).toBe(123456789012.34568);
375+
});
376+
test('two words', () => {
377+
expect(
378+
duckdb.decimal_to_double({
379+
width: 38,
380+
scale: 10,
381+
value: 12345678901234567525491324606797053952n,
382+
})
383+
).toBe(1.2345678901234567e+27);
384+
});
385+
test('negative one word', () => {
386+
expect(
387+
duckdb.decimal_to_double({
388+
width: 18,
389+
scale: 6,
390+
value: -123456789012345680n,
391+
})
392+
).toBe(-123456789012.34568);
393+
});
394+
test('negative two words', () => {
395+
expect(
396+
duckdb.decimal_to_double({
397+
width: 38,
398+
scale: 10,
399+
value: -12345678901234567525491324606797053952n,
400+
})
401+
).toBe(-1.2345678901234567e+27);
402+
});
293403
});
294404
});

0 commit comments

Comments
 (0)