Tedious: 숫자 μœ ν˜•μ˜ 값이 λ²”μœ„λ₯Ό 벗어남

에 λ§Œλ“  2016λ…„ 10μ›” 31일  Β·  5μ½”λ©˜νŠΈ  Β·  좜처: tediousjs/tedious

μœ νš¨ν•œ 숫자 값이 λ‚΄ μ‚½μž… 문을 μ§€λ£¨ν•˜κ²Œ μ°¨λ‹¨ν•˜λŠ” λ¬Έμ œκ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€. λ™μΌν•œ 숫자 값을 μ‚¬μš©ν•˜μ—¬ λ°μ΄ν„°λ² μ΄μŠ€μ— μˆ˜λ™ SQL μ‚½μž…μ΄ μž‘λ™ν•©λ‹ˆλ‹€.

μˆ˜λ™ SQL μ‚½μž…:

INSERT INTO foo (id, foo_id, timestamp, value, value2)  
VALUES ('ff3bd5e2-dbd8-424b-bb4f-455df1fe9f9c', 'b3a0a2c6-3b58-4659-8b4f-9466cede43be',  
        '2016-11-01T00:00:00+00:00', null, 66666.55);

λ‚΄ μ½”λ“œλŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

const bluebird = require('bluebird');
const sql = require('mssql');

const db = require('./database.js');

sql.Promise = bluebird;

const COLUMNS = {
  id: 'id',
  foo_id: 'foo_id',
  timestamp: 'timestamp',
  value: 'value',
  value2: 'value2'
};

exports.create = data => {
  return db.getConnection()
    .then(conn => {
      const ps = new sql.PreparedStatement(conn);
      ps.input(COLUMNS.id, sql.UniqueIdentifier);
      ps.input(COLUMNS.foo_id, sql.UniqueIdentifier);
      ps.input(COLUMNS.timestamp, sql.DateTimeOffset(0));
      ps.input(COLUMNS.value, sql.Numeric(30, 15));
      ps.input(COLUMNS.value2, sql.Numeric(30, 15));

      // Have to convert string to JavaScript date for insert to work
      data.timestamp = new Date(data.timestamp);
      const header = Object.keys(COLUMNS).map(key => COLUMNS[key]).join();

      const query = `INSERT INTO foo (${header}) VALUES (@${COLUMNS.id},` +
        ` @${COLUMNS.foo_id}, @${COLUMNS.timestamp}, @${COLUMNS.value},` +
        ` @${COLUMNS.value2})`;
      return ps.prepare(query)
        .then(() => ps.execute(data)
          // Releases the connection back to the pool
          .then(() => ps.unprepare()))
        .then(() => bluebird.resolve());
    });
};

μ§€λ£¨ν•œ μ½”λ“œλŠ” 66666.55λ₯Ό 값이 66666550000000000000인 μ •μˆ˜λ‘œ λ³€ν™˜ν•˜λŠ” 것 κ°™μŠ΅λ‹ˆλ‹€. 그런 λ‹€μŒ ν•΄λ‹Ή κ°’μ˜ λ‚˜λ¨Έμ§€μ™€ λͺ«μ„ μ·¨ν•˜μ—¬ writeUInt32LEλ₯Ό μ‚¬μš©ν•˜μ—¬ 버퍼에 μ“°λ €κ³  ν•©λ‹ˆλ‹€.

μ§€λ£¨ν•œ μ½”λ“œκ°€ 뢀동 μ†Œμˆ˜μ  숫자λ₯Ό μœ μ§€ν•˜κ³  버퍼에 λ‚΄μž₯ ν•¨μˆ˜ writeDoubleLEλ₯Ό μ‚¬μš©ν•˜λŠ” λŒ€μ‹  뢀동 μ†Œμˆ˜μ  숫자λ₯Ό μ •μˆ˜λ‘œ λ³€ν™˜ν•˜κ³  버퍼에 μ“°λŠ” μ΄μœ λŠ” λ¬΄μ—‡μž…λ‹ˆκΉŒ?

이 문제둜 인해 μ•žμœΌλ‘œ λ‚˜μ•„κ°ˆ 수 μ—†μŠ΅λ‹ˆλ‹€.

known issue

κ°€μž₯ μœ μš©ν•œ λŒ“κΈ€

제 κ²½μš°μ—λŠ” Numeric κ°€ Tedious(Sequelize) μœ„μ— λΌμ΄λΈŒλŸ¬λ¦¬μ— μ˜ν•΄ μ„€μ •λ˜μ—ˆκΈ° λ•Œλ¬Έμ— λ‹€λ₯Έ ν•΄κ²° 방법을 μ‚¬μš©ν•΄μ•Ό ν–ˆμŠ΅λ‹ˆλ‹€. 본질적으둜 10μ§„μˆ˜ λ³€ν™˜μ΄ μ½”λ“œκ°€ μ•„λ‹Œ λ°μ΄ν„°λ² μ΄μŠ€ λ‚΄λΆ€μ—μ„œ λ°œμƒν•˜λ„λ‘ λ§€κ°œλ³€μˆ˜μ˜ ν˜•μ‹μ„ μ§€μ •ν•©λ‹ˆλ‹€. λͺ¨λ“  μˆ«μžμ— λŒ€ν•΄ λ‹€μŒμ„ μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€.

const value_stored = value.toFixed(desired_precision);

μ΄λŠ” 정밀도λ₯Ό μžƒμ§€ μ•Šκ³  λ‚΄κ°€ μ•Œμ§€ λͺ»ν•˜κ±°λ‚˜ μ œμ–΄ν•˜μ§€ λͺ»ν•˜λŠ” 규λͺ¨μ™€ 규λͺ¨μ— λŒ€ν•œ 가정을 ν•˜λŠ” DB와 λ‚΄ μ½”λ“œ 사이에 아무 것도 λ°©ν•΄ν•˜μ§€ μ•Šλ„λ‘ ν•˜κΈ° μœ„ν•œ κ²ƒμž…λ‹ˆλ‹€. λ¬Όλ‘  이것은 μ˜¬λ°”λ₯Έ μœ ν˜•μ„ μ‚¬μš©ν•˜λŠ” 것보닀 λŒ€μ—­ν­ νš¨μœ¨μ„±μ΄ λ–¨μ–΄μ§€μ§€λ§Œ 제 κ²½μš°μ—λŠ” λ ˆμ½”λ“œλ‹Ή <30λ°”μ΄νŠΈ νŽ˜λ„ν‹°κ°€ μ€‘μš”ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

λͺ¨λ“  5 λŒ“κΈ€

뢀동 μ†Œμˆ˜μ  숫자λ₯Ό μ •μˆ˜λ‘œ λ³€ν™˜ν•˜λŠ” μ§€λ£¨ν•œ μ½”λ“œκ°€ μ™œ...

이것이 TDS ν”„λ‘œν† μ½œμ΄ 숫자 값에 λŒ€ν•΄ μš”κ΅¬ν•˜λŠ” 것이기 λ•Œλ¬Έμž…λ‹ˆλ‹€.
2.2.5.5.1.6 10μ§„μˆ˜/숫자

sql.Numeric(30, 15) 경우 _p_λŠ” 30이고 _s_λŠ” 15μž…λ‹ˆλ‹€. λ”°λΌμ„œ μ •μˆ˜λŠ” 666666550000000000000(66666.55 * 10^15)이 되며 16λ°”μ΄νŠΈλ‘œ ν‘œμ‹œλ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€.

https://github.com/tediousjs/tedious/blob/3cb8586c2be217d9b31ef72f866986d85614920d/src/data-type.js#L519

  ...
  buffer.writeUInt64LE(value);        // 8 bytes
  buffer.writeUInt32LE(0x00000000);   // 4 bytes
  buffer.writeUInt32LE(0x00000000);   // 4 bytes

첫 번째 쀄은 λ¬Έμ œκ°€ μžˆλŠ” μ€„μž…λ‹ˆλ‹€.

https://github.com/tediousjs/tedious/blob/master/src/tracking-buffer/writable-tracking-buffer.js#L125
Buffer.writeInt64LEκ°€ μ—†μœΌλ―€λ‘œ 두 개의 32λΉ„νŠΈ 청크둜 μˆ˜ν–‰ν•΄μ•Ό ν•©λ‹ˆλ‹€.

 writeUInt64LE(value) {
    this.writeInt32LE(value & -1);
    return this.writeUInt32LE(Math.floor(value * SHIFT_RIGHT_32));
  }

32λΉ„νŠΈ 였λ₯Έμͺ½ μ‹œν”„νŠΈλŠ” λ¬Έμ œκ°€ λ‚˜νƒ€λ‚˜λŠ” κ³³μž…λ‹ˆλ‹€.
Math.floor(66666550000000000000 * SHIFT_RIGHT_32) λŠ” 0x39D2F2A02 15522015746 λ₯Ό μ‚°μΆœν•©λ‹ˆλ‹€. 34λΉ„νŠΈκ°€ ν•„μš”ν•©λ‹ˆλ‹€.

이제 λ¬Έμ œλŠ” 66666550000000000000이 0x39d2f2a02aed16000이고 64λΉ„νŠΈ 이상이 ν•„μš”ν•˜λ‹€λŠ” κ²ƒμž…λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ 이것은 53λΉ„νŠΈλ₯Ό μ΄ˆκ³Όν•˜λŠ” λͺ¨λ“  μ •μˆ˜κ°€ JavaScript의 _Number_ μœ ν˜•μœΌλ‘œ μ˜¬λ°”λ₯΄κ²Œ ν‘œν˜„λ  수 μžˆλŠ” 것은 μ•„λ‹ˆλΌλŠ” 문제λ₯Ό μ œκΈ°ν•©λ‹ˆλ‹€. 이것을 μ˜¬λ°”λ₯΄κ²Œ μ²˜λ¦¬ν•˜λ €λ©΄ 머리λ₯Ό 긁적이며 λͺ‡ 가지 μƒˆλ‘œμš΄ λ‹¨μœ„ ν…ŒμŠ€νŠΈκ°€ ν•„μš”ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

이 문제λ₯Ό ν•΄κ²°ν•˜λ €λ©΄ 더 μž‘μ€ 규λͺ¨λ₯Ό μ‚¬μš©ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.

@pekim λ‚΄ μ§ˆλ¬Έμ— λ‹΅ν•˜κ³  정보λ₯Ό μ œκ³΅ν•΄ μ£Όμ…”μ„œ κ°μ‚¬ν•©λ‹ˆλ‹€! μ•žμœΌλ‘œ λ‚˜μ•„κ°€κΈ° μœ„ν•΄ Node.js λŒ€μ‹  Python으둜 μž‘μ„±ν•˜κΈ°λ‘œ κ²°μ •ν–ˆμŠ΅λ‹ˆλ‹€. λ‚˜λŠ” μ—¬μ „νžˆ 이 라이브러리λ₯Ό λ‹€λ₯Έ μž‘μ—…μ— μ‚¬μš©ν•  κ²ƒμ΄μ§€λ§Œ 숫자 μœ ν˜•μ€ ν¬ν•¨ν•˜μ§€ μ•Šμ„ κ²ƒμž…λ‹ˆλ‹€. λ‹€λ₯Έ μ‚¬λžŒμ΄ 이 λ¬Έμ œμ— μ§λ©΄ν•˜μ§€ μ•Šλ„λ‘ μ†”λ£¨μ…˜μ„ 찾을 수 있기λ₯Ό λ°”λžλ‹ˆλ‹€.

제 κ²½μš°μ—λŠ” Numeric κ°€ Tedious(Sequelize) μœ„μ— λΌμ΄λΈŒλŸ¬λ¦¬μ— μ˜ν•΄ μ„€μ •λ˜μ—ˆκΈ° λ•Œλ¬Έμ— λ‹€λ₯Έ ν•΄κ²° 방법을 μ‚¬μš©ν•΄μ•Ό ν–ˆμŠ΅λ‹ˆλ‹€. 본질적으둜 10μ§„μˆ˜ λ³€ν™˜μ΄ μ½”λ“œκ°€ μ•„λ‹Œ λ°μ΄ν„°λ² μ΄μŠ€ λ‚΄λΆ€μ—μ„œ λ°œμƒν•˜λ„λ‘ λ§€κ°œλ³€μˆ˜μ˜ ν˜•μ‹μ„ μ§€μ •ν•©λ‹ˆλ‹€. λͺ¨λ“  μˆ«μžμ— λŒ€ν•΄ λ‹€μŒμ„ μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€.

const value_stored = value.toFixed(desired_precision);

μ΄λŠ” 정밀도λ₯Ό μžƒμ§€ μ•Šκ³  λ‚΄κ°€ μ•Œμ§€ λͺ»ν•˜κ±°λ‚˜ μ œμ–΄ν•˜μ§€ λͺ»ν•˜λŠ” 규λͺ¨μ™€ 규λͺ¨μ— λŒ€ν•œ 가정을 ν•˜λŠ” DB와 λ‚΄ μ½”λ“œ 사이에 아무 것도 λ°©ν•΄ν•˜μ§€ μ•Šλ„λ‘ ν•˜κΈ° μœ„ν•œ κ²ƒμž…λ‹ˆλ‹€. λ¬Όλ‘  이것은 μ˜¬λ°”λ₯Έ μœ ν˜•μ„ μ‚¬μš©ν•˜λŠ” 것보닀 λŒ€μ—­ν­ νš¨μœ¨μ„±μ΄ λ–¨μ–΄μ§€μ§€λ§Œ 제 κ²½μš°μ—λŠ” λ ˆμ½”λ“œλ‹Ή <30λ°”μ΄νŠΈ νŽ˜λ„ν‹°κ°€ μ€‘μš”ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

제 κ²½μš°μ—λŠ” Numeric κ°€ Tedious(Sequelize) μœ„μ— λΌμ΄λΈŒλŸ¬λ¦¬μ— μ˜ν•΄ μ„€μ •λ˜μ—ˆκΈ° λ•Œλ¬Έμ— λ‹€λ₯Έ ν•΄κ²° 방법을 μ‚¬μš©ν•΄μ•Ό ν–ˆμŠ΅λ‹ˆλ‹€. 본질적으둜 10μ§„μˆ˜ λ³€ν™˜μ΄ μ½”λ“œκ°€ μ•„λ‹Œ λ°μ΄ν„°λ² μ΄μŠ€ λ‚΄λΆ€μ—μ„œ λ°œμƒν•˜λ„λ‘ λ§€κ°œλ³€μˆ˜μ˜ ν˜•μ‹μ„ μ§€μ •ν•©λ‹ˆλ‹€. λͺ¨λ“  μˆ«μžμ— λŒ€ν•΄ λ‹€μŒμ„ μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€.

const value_stored = value.toFixed(desired_precision);

μ΄λŠ” 정밀도λ₯Ό μžƒμ§€ μ•Šκ³  λ‚΄κ°€ μ•Œμ§€ λͺ»ν•˜κ±°λ‚˜ μ œμ–΄ν•˜μ§€ λͺ»ν•˜λŠ” 규λͺ¨μ™€ 규λͺ¨μ— λŒ€ν•œ 가정을 ν•˜λŠ” DB와 λ‚΄ μ½”λ“œ 사이에 아무 것도 λ°©ν•΄ν•˜μ§€ μ•Šλ„λ‘ ν•˜κΈ° μœ„ν•œ κ²ƒμž…λ‹ˆλ‹€. λ¬Όλ‘  이것은 μ˜¬λ°”λ₯Έ μœ ν˜•μ„ μ‚¬μš©ν•˜λŠ” 것보닀 λŒ€μ—­ν­ νš¨μœ¨μ„±μ΄ λ–¨μ–΄μ§€μ§€λ§Œ 제 κ²½μš°μ—λŠ” λ ˆμ½”λ“œλ‹Ή <30λ°”μ΄νŠΈ νŽ˜λ„ν‹°κ°€ μ€‘μš”ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

그것은 λ‚˜μ—κ²Œλ„ 문제λ₯Ό ν•΄κ²°ν–ˆμŠ΅λ‹ˆλ‹€.

이것을 μ „μ—­μ μœΌλ‘œ μ μš©ν•  수 μžˆλŠ” 방법이 μžˆμŠ΅λ‹ˆκΉŒ? λ”°λΌμ„œ 연속성이 .toFixed(2)λ₯Ό ν˜ΈμΆœν•˜λŠ” μ‹­μ§„μˆ˜ 값을 μ‚½μž…/μ—…λ°μ΄νŠΈν•˜λ €κ³  ν•  λ•Œλ§ˆλ‹€?

이 νŽ˜μ΄μ§€κ°€ 도움이 λ˜μ—ˆλ‚˜μš”?
0 / 5 - 0 λ“±κΈ‰