Skip to content

Commit

Permalink
decimal conversion
Browse files Browse the repository at this point in the history
  • Loading branch information
jraymakers committed Sep 1, 2024
1 parent 6123a67 commit 722dd1d
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 36 deletions.
110 changes: 74 additions & 36 deletions bindings/src/duckdb_node_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,65 @@ duckdb_timestamp_struct GetTimestampPartsFromObject(Napi::Object timestamp_parts
return { date, time };
}

duckdb_hugeint GetHugeIntFromBigInt(Napi::Env env, Napi::BigInt bigint) {
int sign_bit;
size_t word_count = 2;
uint64_t words[2];
bigint.ToWords(&sign_bit, &word_count, words);
if (word_count > 2) {
throw Napi::Error::New(env, "bigint out of hugeint range");
}
uint64_t lower = word_count > 0 ? (sign_bit ? -1 : 1) * words[0] : 0;
int64_t upper = word_count > 1 ? (sign_bit ? -1 : 1) * words[1] : (word_count > 0 && sign_bit ? -1 : 0);
return { lower, upper };
}

Napi::BigInt MakeBigIntFromHugeInt(Napi::Env env, duckdb_hugeint hugeint) {
int sign_bit = hugeint.upper < 0 ? 1 : 0;
size_t word_count = hugeint.upper == -1 ? 1 : 2;
uint64_t words[2];
words[0] = (sign_bit ? -1 : 1) * hugeint.lower;
words[1] = (sign_bit ? -1 : 1) * hugeint.upper;
return Napi::BigInt::New(env, sign_bit, word_count, words);
}

duckdb_uhugeint GetUHugeIntFromBigInt(Napi::Env env, Napi::BigInt bigint) {
int sign_bit;
size_t word_count = 2;
uint64_t words[2];
bigint.ToWords(&sign_bit, &word_count, words);
if (word_count > 2 || sign_bit) {
throw Napi::Error::New(env, "bigint out of uhugeint range");
}
uint64_t lower = word_count > 0 ? words[0] : 0;
uint64_t upper = word_count > 1 ? words[1] : 0;
return { lower, upper };
}

Napi::BigInt MakeBigIntFromUHugeInt(Napi::Env env, duckdb_uhugeint uhugeint) {
int sign_bit = 0;
size_t word_count = 2;
uint64_t words[2];
words[0] = uhugeint.lower;
words[1] = uhugeint.upper;
return Napi::BigInt::New(env, sign_bit, word_count, words);
}

Napi::Object MakeDecimalObject(Napi::Env env, duckdb_decimal decimal) {
auto decimal_obj = Napi::Object::New(env);
decimal_obj.Set("width", Napi::Number::New(env, decimal.width));
decimal_obj.Set("scale", Napi::Number::New(env, decimal.scale));
decimal_obj.Set("value", MakeBigIntFromHugeInt(env, decimal.value));
return decimal_obj;
}

duckdb_decimal GetDecimalFromObject(Napi::Env env, Napi::Object decimal_obj) {
uint8_t width = decimal_obj.Get("width").As<Napi::Number>().Uint32Value();
uint8_t scale = decimal_obj.Get("scale").As<Napi::Number>().Uint32Value();
auto value = GetHugeIntFromBigInt(env, decimal_obj.Get("value").As<Napi::BigInt>());
return { width, scale, value };
}

// Externals

template<typename T>
Expand Down Expand Up @@ -1230,17 +1289,8 @@ class DuckDBNodeAddon : public Napi::Addon<DuckDBNodeAddon> {
// function hugeint_to_double(hugeint: bigint): number
Napi::Value hugeint_to_double(const Napi::CallbackInfo& info) {
auto env = info.Env();
auto hugeint_as_bigint = info[0].As<Napi::BigInt>();
int sign_bit;
size_t word_count = 2;
uint64_t words[2];
hugeint_as_bigint.ToWords(&sign_bit, &word_count, words);
if (word_count > 2) {
throw Napi::Error::New(env, "bigint out of hugeint range");
}
uint64_t lower = word_count > 0 ? (sign_bit ? -1 : 1) * words[0] : 0;
int64_t upper = word_count > 1 ? (sign_bit ? -1 : 1) * words[1] : (word_count > 0 && sign_bit ? -1 : 0);
duckdb_hugeint hugeint = { lower, upper };
auto bigint = info[0].As<Napi::BigInt>();
auto hugeint = GetHugeIntFromBigInt(env, bigint);
auto output_double = duckdb_hugeint_to_double(hugeint);
return Napi::Number::New(env, output_double);
}
Expand All @@ -1251,29 +1301,15 @@ class DuckDBNodeAddon : public Napi::Addon<DuckDBNodeAddon> {
auto env = info.Env();
auto input_double = info[0].As<Napi::Number>().DoubleValue();
auto hugeint = duckdb_double_to_hugeint(input_double);
int sign_bit = input_double < 0 ? 1 : 0;
size_t word_count = hugeint.upper == -1 ? 1 : 2;
uint64_t words[2];
words[0] = (sign_bit ? -1 : 1) * hugeint.lower;
words[1] = (sign_bit ? -1 : 1) * hugeint.upper;
return Napi::BigInt::New(env, sign_bit, word_count, words);
return MakeBigIntFromHugeInt(env, hugeint);
}

// DUCKDB_API double duckdb_uhugeint_to_double(duckdb_uhugeint val);
// function uhugeint_to_double(uhugeint: bigint): number
Napi::Value uhugeint_to_double(const Napi::CallbackInfo& info) {
auto env = info.Env();
auto uhugeint_as_bigint = info[0].As<Napi::BigInt>();
int sign_bit;
size_t word_count = 2;
uint64_t words[2];
uhugeint_as_bigint.ToWords(&sign_bit, &word_count, words);
if (word_count > 2 || sign_bit) {
throw Napi::Error::New(env, "bigint out of uhugeint range");
}
uint64_t lower = word_count > 0 ? words[0] : 0;
uint64_t upper = word_count > 1 ? words[1] : 0;
duckdb_uhugeint uhugeint = { lower, upper };
auto bigint = info[0].As<Napi::BigInt>();
auto uhugeint = GetUHugeIntFromBigInt(env, bigint);
auto output_double = duckdb_uhugeint_to_double(uhugeint);
return Napi::Number::New(env, output_double);
}
Expand All @@ -1284,26 +1320,28 @@ class DuckDBNodeAddon : public Napi::Addon<DuckDBNodeAddon> {
auto env = info.Env();
auto input_double = info[0].As<Napi::Number>().DoubleValue();
auto uhugeint = duckdb_double_to_uhugeint(input_double);
int sign_bit = 0;
size_t word_count = 2;
uint64_t words[2];
words[0] = uhugeint.lower;
words[1] = uhugeint.upper;
return Napi::BigInt::New(env, sign_bit, word_count, words);
return MakeBigIntFromUHugeInt(env, uhugeint);
}

// DUCKDB_API duckdb_decimal duckdb_double_to_decimal(double val, uint8_t width, uint8_t scale);
// function double_to_decimal(double: number, width: number, scale: number): Decimal
Napi::Value double_to_decimal(const Napi::CallbackInfo& info) {
auto env = info.Env();
throw Napi::Error::New(env, "Not implemented yet");
auto input_double = info[0].As<Napi::Number>().DoubleValue();
auto width = info[1].As<Napi::Number>().Uint32Value();
auto scale = info[2].As<Napi::Number>().Uint32Value();
auto decimal = duckdb_double_to_decimal(input_double, width, scale);
return MakeDecimalObject(env, decimal);
}

// DUCKDB_API double duckdb_decimal_to_double(duckdb_decimal val);
// function decimal_to_double(decimal: Decimal): number
Napi::Value decimal_to_double(const Napi::CallbackInfo& info) {
auto env = info.Env();
throw Napi::Error::New(env, "Not implemented yet");
auto decimal_obj = info[0].As<Napi::Object>();
auto decimal = GetDecimalFromObject(env, decimal_obj);
auto output_double = duckdb_decimal_to_double(decimal);
return Napi::Number::New(env, output_double);
}

// DUCKDB_API duckdb_state duckdb_prepare(duckdb_connection connection, const char *query, duckdb_prepared_statement *out_prepared_statement);
Expand Down
110 changes: 110 additions & 0 deletions bindings/test/conversion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,12 @@ suite('conversion', () => {
test('near max', () => {
expect(duckdb.double_to_hugeint(1.7014118346046922e+38)).toBe(2n ** 127n - 2n ** 74n);
});
test('out of range (positive)', () => {
expect(duckdb.double_to_hugeint(1.8e+38)).toBe(0n);
});
test('out of range (negative)', () => {
expect(duckdb.double_to_hugeint(-1.8e+38)).toBe(0n);
});
});
suite('uhugeint_to_double', () => {
test('zero', () => {
Expand Down Expand Up @@ -290,5 +296,109 @@ suite('conversion', () => {
test('near max', () => {
expect(duckdb.double_to_uhugeint(1.7014118346046922e+38)).toBe(2n ** 127n - 2n ** 74n);
});
test('out of range (positive)', () => {
expect(duckdb.double_to_uhugeint(3.5e+38)).toBe(0n);
});
test('out of range (negative)', () => {
expect(duckdb.double_to_uhugeint(-1)).toBe(0n);
});
});
suite('double_to_decimal', () => {
test('zero', () => {
expect(duckdb.double_to_decimal(0, 4, 1)).toStrictEqual({ width: 4, scale: 1, value: 0n });
});
test('one', () => {
expect(duckdb.double_to_decimal(1, 9, 4)).toStrictEqual({ width: 9, scale: 4, value: 10000n });
});
test('negative one', () => {
expect(duckdb.double_to_decimal(-1, 9, 4)).toStrictEqual({ width: 9, scale: 4, value: -10000n });
});
test('one word', () => {
expect(duckdb.double_to_decimal(123456789012.34568, 18, 6)).toStrictEqual(
{ width: 18, scale: 6, value: 123456789012345680n }
);
});
test('two words', () => {
expect(duckdb.double_to_decimal(1.2345678901234567e+27, 38, 10)).toStrictEqual(
{ width: 38, scale: 10, value: 12345678901234567525491324606797053952n }
);
});
test('negative one word', () => {
expect(duckdb.double_to_decimal(-123456789012.34568, 18, 6)).toStrictEqual(
{ width: 18, scale: 6, value: -123456789012345680n }
);
});
test('negative two words', () => {
expect(duckdb.double_to_decimal(-1.2345678901234567e+27, 38, 10)).toStrictEqual(
{ width: 38, scale: 10, value: -12345678901234567525491324606797053952n }
);
});
test('out of range (positive)', () => {
expect(duckdb.double_to_decimal(1e+38, 38, 0)).toStrictEqual(
{ width: 0, scale: 0, value: 0n }
);
});
test('out of range (negative)', () => {
expect(duckdb.double_to_decimal(-1e+38, 38, 0)).toStrictEqual(
{ width: 0, scale: 0, value: 0n }
);
});
test('out of range (width)', () => {
expect(duckdb.double_to_decimal(1, 39, 0)).toStrictEqual(
{ width: 0, scale: 0, value: 0n }
);
});
test('out of range (scale)', () => {
expect(duckdb.double_to_decimal(1, 4, 4)).toStrictEqual(
{ width: 0, scale: 0, value: 0n }
);
});
});
suite('decimal_to_double', () => {
test('zero', () => {
expect(duckdb.decimal_to_double({ width: 4, scale: 1, value: 0n })).toBe(0);
});
test('one', () => {
expect(duckdb.decimal_to_double({ width: 9, scale: 4, value: 10000n })).toBe(1);
});
test('negative one', () => {
expect(duckdb.decimal_to_double({ width: 9, scale: 4, value: -10000n })).toBe(-1);
});
test('one word', () => {
expect(
duckdb.decimal_to_double({
width: 18,
scale: 6,
value: 123456789012345680n,
})
).toBe(123456789012.34568);
});
test('two words', () => {
expect(
duckdb.decimal_to_double({
width: 38,
scale: 10,
value: 12345678901234567525491324606797053952n,
})
).toBe(1.2345678901234567e+27);
});
test('negative one word', () => {
expect(
duckdb.decimal_to_double({
width: 18,
scale: 6,
value: -123456789012345680n,
})
).toBe(-123456789012.34568);
});
test('negative two words', () => {
expect(
duckdb.decimal_to_double({
width: 38,
scale: 10,
value: -12345678901234567525491324606797053952n,
})
).toBe(-1.2345678901234567e+27);
});
});
});

0 comments on commit 722dd1d

Please sign in to comment.