将 Oracle 的数字转换为字节,然后转换为值 + 指数
Posted
技术标签:
【中文标题】将 Oracle 的数字转换为字节,然后转换为值 + 指数【英文标题】:Converting Oracle's Number toBytes and then to Value + Exponent 【发布时间】:2019-08-30 08:04:53 【问题描述】:我一直在网上寻找如何将 Oracle 数字转换为我可以使用的东西,但是我完全找不到任何人设法提出一个完整的算法(尤其是负指数) .
一些信息已发布在https://gotodba.com/2015/03/24/how-are-numbers-saved-in-oracle/ 上,但这是不完整的,因为它无法描述必须再次位翻转的负指数解决方案。下面是我想出的代码,使它能够成功处理(到目前为止)。我测试了从 -10,000 到 999,999 和 0.0001 到 2.0002 的值。
struct ValueExponent
int64_t value = 0;
int32_t exponent = 0; // Powers of 10
;
int32_t CalculateExponent(const ::oracle::occi::Bytes& bytes, bool& isNegative)
int workingExponent = static_cast<int>(bytes.byteAt(0));
isNegative = (workingExponent & 0x80) == 0;
if (isNegative)
workingExponent = ~workingExponent;
bool isNegativeExponent = (workingExponent & 0x40) == 0;
if (isNegativeExponent)
workingExponent = ~workingExponent;
return ((isNegativeExponent ? -1 : 1) * (workingExponent & 0x3f)) - (isNegativeExponent ? 1 : 0);
ValueExponent OracleNumberToValueExponent(const ::oracle::occi::Number& num)
auto bytes = num.toBytes();
int64_t value = 0;
bool isNegative = false;
int32_t exponent = CalculateExponent(bytes, isNegative);
decltype(bytes.length()) max = isNegative ? bytes.length() - 1 : bytes.length();
for(decltype(bytes.length()) ix = 1; ix < max; ++ix)
auto borig = bytes.byteAt(ix);
int b = static_cast<int>(borig);
b -= 1;
if (isNegative)
b = 100 - b;
value = value * 100 + b;
--exponent;
ValueExponent retval;
retval.value = isNegative ? -value : value;
retval.exponent = exponent * 2; //Oracle exponents are of 100 not 10
return retval;
我相信这应该能够输出任何需要的东西,但是任何对改进算法提出建议的人都会非常感激。
【问题讨论】:
我不懂 c++,所以无法猜测您希望代码的输出是什么样的,但 Oracle 可以将数字转换为字符串,包括科学格式,如果有帮助?例如to_char(nbr, '0.99999EEEE')
感谢您对 to_char 的帮助,但我希望将其转换为值指数形式以与我的其他代码兼容,这些代码适用于我的浮点数,我宁愿避免首先转换为字符串以节省一些处理能力,因为它每秒可以处理数千个。
您期望的输出是什么?例如。如果原始数字是 -10,000,您期望的输出是多少?
以 -10,000 为例,我期望任何一个:值:-1 指数:4;值:-10 指数:3;值:-100 指数:2;值:-1000 指数:1;值:-10000 指数:0;如果是 -0.1,我希望是这样的:值:-1 指数:-1
正在从数据库中检索您的号码?
【参考方案1】:
假设您的数字来自数据库,您可以在字符串上应用逻辑,而不是重新发明如何导出值和指数,例如:
WITH nums AS (SELECT -10000 nbr from dual UNION ALL
SELECT -0.99 nbr FROM dual UNION ALL
SELECT 0 nbr FROM dual UNION ALL
SELECT 0.0001 nbr FROM dual UNION ALL
SELECT 0.99 nbr FROM dual UNION ALL
SELECT 2.0002 nbr FROM dual UNION ALL
SELECT 999999 nbr FROM dual UNION ALL
SELECT 1e23 nbr FROM dual)
SELECT nbr,
sci_num,
TO_NUMBER(SUBSTR(sci_num, 1, INSTR(sci_num, 'E', 1, 1) - 1)) val,
TO_NUMBER(SUBSTR(sci_num, INSTR(sci_num, 'E', 1, 1) + 1)) EXP
FROM (SELECT nbr,
to_char(nbr, 'fm0D099999999999999999EEEE') sci_num
FROM nums);
NBR SCI_NUM VAL EXP
---------- -------------------------- ---------- ----------
-10000 -1.0E+04 -1 4
-0.99 -9.9E-01 -9.9 -1
0 0.0E+00 0 0
0.0001 1.0E-04 1 -4
0.99 9.9E-01 9.9 -1
2.0002 2.0002E+00 2.0002 0
999999 9.99999E+05 9.99999 5
1E23 1.0E+23 1 23
(nums
子查询只是生成一些测试数据以在查询中使用的一种方式。)
如果您需要在 PL/SQL 中实现此逻辑,您可以执行以下操作:
DECLARE
v_num NUMBER := -10001;
v_val NUMBER;
v_exp NUMBER;
v_sci_fmt_num VARCHAR2(100);
BEGIN
v_sci_fmt_num := to_char(v_num, 'fm0D099999999999999999EEEE');
v_val := TO_NUMBER(SUBSTR(v_sci_fmt_num, 1, INSTR(v_sci_fmt_num, 'E', 1, 1) - 1));
v_exp := TO_NUMBER(SUBSTR(v_sci_fmt_num, INSTR(v_sci_fmt_num, 'E', 1, 1) + 1));
-- dummy output
dbms_output.put_line('number: '||v_num||'; value = '||v_val||', exponent = '||v_exp);
END;
/
number: -10001; value = -1.0001, exponent = 4
【讨论】:
谢谢;这是一个很棒的解决方案,但是我无法控制 SQL(它是跨数据库的),所以我只知道数据库已经给我发回了一个 oracle 号码,我现在需要将它作为内部表示的数量发回,值得深思的是我是否能以某种方式完成这项工作 是否没有内置的 c++ 函数(或某些库或其他东西)可以将数字转换为科学格式,即使该数字起源于 Oracle?【参考方案2】:看Oracle自己的ODPI-C项目代码dpiDataBuffer__fromOracleNumberAsText()。
【讨论】:
以上是关于将 Oracle 的数字转换为字节,然后转换为值 + 指数的主要内容,如果未能解决你的问题,请参考以下文章