Skip to content

fix: Improve ergonomics for the Decimal type#341

Open
Divyanshu-s13 wants to merge 4 commits into
apache:mainfrom
Divyanshu-s13:decimal-type-ergonomics-js-81
Open

fix: Improve ergonomics for the Decimal type#341
Divyanshu-s13 wants to merge 4 commits into
apache:mainfrom
Divyanshu-s13:decimal-type-ergonomics-js-81

Conversation

@Divyanshu-s13

@Divyanshu-s13 Divyanshu-s13 commented Nov 18, 2025

Copy link
Copy Markdown
Contributor

What's Changed

Added a new decimal.ts module that provides high-level utilities for working with Decimal128 and Decimal256 values in JavaScript. These utilities make decimal handling more ergonomic by removing the need for low-level bit manipulation and aligning behavior with Arrow C++ semantics. The module is exported through the util namespace and integrates cleanly without introducing breaking changes.

Closes #81.

@Divyanshu-s13

Divyanshu-s13 commented Nov 18, 2025

Copy link
Copy Markdown
Contributor Author

@kou Fix #81: Improve ergonomics for the Decimal type in JavaScript bindings.

@domoritz domoritz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the pull request. This definitely needs tests.

Also, does this address #100 and #77 in some way?

- Comprehensive tests for isNegativeDecimal, negateDecimal
- Tests for toDecimalString and toDecimalNumber conversions
- Tests for fromDecimalString parsing with roundtrips
- Integration tests for negation and edge cases
@Divyanshu-s13

Divyanshu-s13 commented Nov 20, 2025

Copy link
Copy Markdown
Contributor Author

Thanks for the pull request. This definitely needs tests.

Also, does this address #100 and #77 in some way?

thanks for review!
I have added a test case for this.

This partially addresses Issues #77 and #100 by providing comprehensive utility functions for working with Decimal types in Apache Arrow JavaScript.

@kou kou changed the title Fix #81: Improve ergonomics for the Decimal type in JavaScript bindings fix: Improve ergonomics for the Decimal type Nov 22, 2025
@domoritz

Copy link
Copy Markdown
Member

Thanks for these utilities. I think we should try to use them in the default converters so that #77 gets fixed. Either you could do this here or we can do it in a follow up when you fixed the CI errors and we merged.

@Divyanshu-s13

Copy link
Copy Markdown
Contributor Author

Thanks for these utilities. I think we should try to use them in the default converters so that #77 gets fixed. Either you could do this here or we can do it in a follow up when you fixed the CI errors and we merged.

I have fixed CI error can you merge it.

@kou

kou commented Nov 25, 2025

Copy link
Copy Markdown
Member

Could you check lint failures?

https://github.com/apache/arrow-js/actions/runs/19644239168/job/56300291328?pr=341#step:5:8

/home/runner/work/arrow-js/arrow-js/src/util/decimal.ts
Error:    84:9  error  'n' is never reassigned. Use 'const' instead                 prefer-const
Error:    89:5  error  This `if` statement can be replaced by a ternary expression  unicorn/prefer-ternary
Error:   128:5  error  This `if` statement can be replaced by a ternary expression  unicorn/prefer-ternary

FYI: You can run CI on your fork by enabling GitHub Actions on your fork.

- Change 'let n' to 'const n' (prefer-const)
- Use ternary for magnitude assignment (unicorn/prefer-ternary)
- Use ternary for result composition (unicorn/prefer-ternary)
@Divyanshu-s13

Copy link
Copy Markdown
Contributor Author

Could you check lint failures?

https://github.com/apache/arrow-js/actions/runs/19644239168/job/56300291328?pr=341#step:5:8

/home/runner/work/arrow-js/arrow-js/src/util/decimal.ts
Error:    84:9  error  'n' is never reassigned. Use 'const' instead                 prefer-const
Error:    89:5  error  This `if` statement can be replaced by a ternary expression  unicorn/prefer-ternary
Error:   128:5  error  This `if` statement can be replaced by a ternary expression  unicorn/prefer-ternary

FYI: You can run CI on your fork by enabling GitHub Actions on your fork.

I have fix this can you merge it.

@Divyanshu-s13

Copy link
Copy Markdown
Contributor Author

@kou @domoritz I have fix all CI failure and all checks are passed please merge it.

Comment thread src/util/decimal.ts
}

// Calculate divisor as 10^scale
// Using a loop instead of BigInt exponentiation (**) for ES2015 compatibility

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we bump to something beyond es2015 for better code?

@ilyabo

ilyabo commented Jan 13, 2026

Copy link
Copy Markdown

It might be worth it to include Decimal256 support (as suggested by Coderabbit)

@Divyanshu-s13

Copy link
Copy Markdown
Contributor Author

@kou @domoritz please review it.

@domoritz

domoritz commented Feb 4, 2026

Copy link
Copy Markdown
Member

I started looking at this in November. Have you seen my question in #341 (comment)?

@domoritz domoritz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making these utilities. I wonder, can we also use some of these utilities in other tests that already exist?

Comment thread src/util/decimal.ts
* ```
* @ignore
*/
export function toDecimalString(value: Uint32Array, scale: number): string {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you test this with Decimal256? I'm not 100% sure this method support it.

Comment thread src/util/decimal.ts
}

// Trim trailing zeros in fractional part
fracPart = fracPart.replace(/0+$/, '');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this mean that a scale of 5 would still return "0.0" rather than "0.00000"?

Comment thread src/util/decimal.ts
* ```
* @ignore
*/
export function fromDecimalString(str: string, scale: number): Uint32Array {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If toDecimalString supports Decimal256, shouldn't this support it as well?

Comment thread src/util/decimal.ts
const [wholePart = '0', fracPart = ''] = str.split('.');

// Pad or truncate fractional part to match scale
const adjustedFrac = (fracPart + '0'.repeat(scale)).slice(0, scale);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the input string has more fractional digits than scale, are they silently truncated without rounding?

Comment thread src/util/decimal.ts
};

// Detect sign via MSB of most-significant word
const isNegative = (value[3] & 0x80000000) !== 0;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this not the same as isNegativeDecimal?

@ianmcook

ianmcook commented Jun 9, 2026

Copy link
Copy Markdown
Member

I think I’m hitting the same underlying issue through Table.toString(): the decimal type and scale are preserved, but stringification prints the raw unscaled integer:

import { Decimal, makeTable, makeVector } from 'apache-arrow';

const type = new Decimal(2, 5, 128); // scale, precision, bitWidth

// Unscaled integer 1999, representing 19.99 with scale 2.
const price = makeVector({
  type,
  data: new Uint32Array([1999, 0, 0, 0]),
});

const table = makeTable({ price: price });

console.log(String(price.type));
console.log('value.toString():', String(price.get(0)));
console.log('value.valueOf(scale):', price.get(0).valueOf(type.scale));
console.log(table.toString());

Actual output:

Decimal[5e+2]
value.toString(): 1999
value.valueOf(scale): 19.99
[
  {"price": 1999}
]

Expected: Table.toString() would format the decimal using the field type’s scale, e.g. 19.99, matching the logical decimal128(5, 2) value.

For comparison, PyArrow prints the same logical value using scale metadata:

from decimal import Decimal
import pyarrow as pa

table = pa.table({
    "price": pa.array([Decimal("19.99")], type=pa.decimal128(5, 2)),
})

print(table)

Output:

pyarrow.Table
price: decimal128(5, 2)
----
price: [[19.99]]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[JS] Ergonomics for the Decimal type via the JavaScript bindings

5 participants