Skip to content

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

Open
@dylanscott

Description

@dylanscott

Describe the enhancement requested

I really love the Arrow project and have gotten an enormous amount of value from it, and I realize this is essentially a duplicate of #100 but it's been a couple of years and I just want to reiterate how difficult it is to properly handle values with the Decimal type from the JavaScript bindings. All you get is very low-level access to the underlying fixed-width buffer and you have to dig through the Arrow source to figure out how to do anything non-trivial with them. In my use-case all I need to do is accurately stringify them and have just had to fix the 3rd bug I've hit with my implementation (handling scale=0). Here is what I've arrived at, but I wish I had more confidence that there aren't more bugs lurking (there's no way it handles Decimal256 properly, but I've never encountered them in the wild):

import { util } from "apache-arrow";
import { trimEnd } from "lodash";

function formatDecimal(value: Uint32Array, scale: number): string {
  const negative = isNegative(value);
  const sign = negative ? "-" : "";
  if (negative) {
    negate(value);
  }

  const str = util.bignumToString(new util.BN(value)).padStart(scale, "0");
  if (scale === 0) {
    return `${sign}${str}`;
  }

  const wholePart = str.slice(0, -scale) || "0";
  const decimalPart = trimEnd(str.slice(-scale), "0") || "0";
  return `${sign}${wholePart}.${decimalPart}`;
}

const MAX_INT32 = 2 ** 31 - 1;
function isNegative(value: Uint32Array): boolean {
  // https://github.com/apache/arrow/blob/3600bd802fbf5eb0c6e00bdae7939bcc3d631eb1/cpp/src/arrow/util/basic_decimal.h#L125
  return value[value.length - 1] > MAX_INT32;
}

function negate(value: Uint32Array): void {
  // https://github.com/apache/arrow/blob/3600bd802fbf5eb0c6e00bdae7939bcc3d631eb1/cpp/src/arrow/util/basic_decimal.cc#L1144
  let carry = 1;
  for (let i = 0; i < value.length; i++) {
    const elem = value[i];
    const updated = ~elem + carry;
    value[i] = updated;
    carry &= elem === 0 ? 1 : 0;
  }
}

Component(s)

JavaScript

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions