如何在 TSQL 中读取/写入 int 或 bigint 位掩码字段的完整 32 或 64 位

发布于 2024-09-29 23:37:07 字数 1159 浏览 0 评论 0原文

设置第 32 位和第 64 位很棘手。

32 位解决方案:

我让它适用于 32 位字段。诀窍是在将 POWER 函数的返回值转换为 int 之前将其转换为 binary(4)。如果您尝试直接转换为 int,而不先转换为 binary(4),则在对第 32 位(索引 31)进行操作时将出现算术溢出异常。另外,您必须确保传递给 POWER 的表达式具有足够大的类型(例如 bigint)来存储最大返回值 (2^31),否则 POWER 函数将引发算术溢出异常。

CREATE FUNCTION [dbo].[SetIntBit] 
(
    @bitfieldvalue int,
    @bitindex int, --(0 to 31)
    @bit bit --(0 or 1)
)
RETURNS int
AS
BEGIN
    DECLARE @bitmask int = CAST(CAST(POWER(CAST(2 as bigint),@bitindex) as binary(4)) as int);
    RETURN
    CASE
        WHEN @bit = 1 THEN (@bitfieldvalue | @bitmask)
        WHEN @bit = 0 THEN (@bitfieldvalue & ~@bitmask)
        ELSE @bitfieldvalue --NO CHANGE
    END
END

64 位问题:

我打算对 64 位字段使用类似的方法,但是我发现 POWER 函数返回的值不准确,尽管表达式/返回值使用了decimal(38) 类型。 例如:“select POWER(CAST(2 asdecimal(38)), 64)”返回 18446744073709552000 (只有前 16 位数字是准确的),而不是正确的值 18446744073709551616。即使我只将 2 加到63次方,这个结果仍然不准确。

POWER 函数的文档指出“如果使用货币或数字数据类型,则内部转换为浮点可能会导致精度损失”。 (请注意,数字类型在功能上等同于十进制类型)。

我认为正确处理 64 位字段的唯一方法是对它们的 32 位部分进行操作,但这需要对 @bitindex 属性进行额外检查,以了解我需要对哪一半进行操作。是否有任何内置函数或更好的方法来显式设置 TSQL 中 32 位和 64 位位掩码字段中的最终位?

Setting the 32nd and 64th bits is tricky.

32-bit Solution:

I got it to work for 32-bit fields. The trick is to cast the return value of the POWER function to binary(4) before casting it to int. If you try to cast directly to int, without first casting to binary(4), you will get an arithmetic overflow exception when operating on the 32nd bit (index 31). Also, you must ensure the expression passed to POWER is of a sufficiently large type (e.g. bigint) to store the maximum return value (2^31), or the POWER function will throw an arithmetic overflow exception.

CREATE FUNCTION [dbo].[SetIntBit] 
(
    @bitfieldvalue int,
    @bitindex int, --(0 to 31)
    @bit bit --(0 or 1)
)
RETURNS int
AS
BEGIN
    DECLARE @bitmask int = CAST(CAST(POWER(CAST(2 as bigint),@bitindex) as binary(4)) as int);
    RETURN
    CASE
        WHEN @bit = 1 THEN (@bitfieldvalue | @bitmask)
        WHEN @bit = 0 THEN (@bitfieldvalue & ~@bitmask)
        ELSE @bitfieldvalue --NO CHANGE
    END
END

64-bit Problem:

I was going to use a similar approach for 64-bit fields, however I'm finding that the POWER function is returning inaccurate values, despite using the decimal(38) type for the expression/return value.
For example: "select POWER(CAST(2 as decimal(38)), 64)" returns 18446744073709552000 (only the first 16 digits are accurate) rather than the correct value of 18446744073709551616. And even though I'd only raise 2 to the 63rd power, that result is still inaccurate.

The documentation of the POWER function indicates that "Internal conversion to float can cause loss of precision if either the money or numeric data types are used." (note that numeric type is functionally equivalent to decimal type).

I think the only way to handle 64-bit fields properly is to operate on their 32-bit halves, but that involves an extra check on the @bitindex property to see which half I need to operate on. Are there any built-in function or better ways to explicitly set those final bits in 32-bit and 64-bit bitmasked fields in TSQL?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

缘字诀 2024-10-06 23:37:07

64 位解决方案:

到目前为止,我对自己的问题能想到的最简单的解决方案是为第 64 位(即 2^63)的位掩码计算添加一个例外情况,其中位掩码值是硬编码的,因此它不必由 POWER 计算。据我所知,POWER 可以准确地计算 2^62 和更小的值。

CREATE FUNCTION [dbo].[SetBigIntBit] 
(
    @bitfieldvalue bigint,
    @bitindex int, --(0 to 63)
    @bit bit --(0 or 1)
)
RETURNS bigint
AS
BEGIN
    DECLARE @bitmask bigint = case WHEN @bitindex = 63 THEN CAST(0x8000000000000000 as bigint)
    ELSE CAST(CAST(POWER(CAST(2 as bigint),@bitindex) as binary(8)) as bigint)
    RETURN
    CASE
        WHEN @bit = 1 THEN (@bitfieldvalue | @bitmask)
        WHEN @bit = 0 THEN (@bitfieldvalue & ~@bitmask)
        ELSE @bitfieldvalue --NO CHANGE
    END
END

编辑:这是一些测试上述功能的代码......

declare @bitfield bigint = 0;
print @bitfield;
declare @bitindex int;
set @bitindex = 0;
while @bitindex < 64
begin
  set @bitfield = tutor.dbo.SetBigIntBit(@bitfield,@bitindex,1);
  print @bitfield;  
  set @bitindex = @bitindex + 1;
end
set @bitindex = 0;
while @bitindex < 64
begin
  set @bitfield = tutor.dbo.SetBigIntBit(@bitfield,@bitindex,0);
  print @bitfield;  
  set @bitindex = @bitindex + 1;
end

64-bit Solution:

So far, the simplest solution I can come up with to my own question is to add an exceptional case for problematic computation of the bitmask for the 64th bit (i.e. 2^63), where the bitmask value is hardcoded so that it does not have to be computed by POWER. POWER computes 2^62 and smaller values accurately as far as I can see.

CREATE FUNCTION [dbo].[SetBigIntBit] 
(
    @bitfieldvalue bigint,
    @bitindex int, --(0 to 63)
    @bit bit --(0 or 1)
)
RETURNS bigint
AS
BEGIN
    DECLARE @bitmask bigint = case WHEN @bitindex = 63 THEN CAST(0x8000000000000000 as bigint)
    ELSE CAST(CAST(POWER(CAST(2 as bigint),@bitindex) as binary(8)) as bigint)
    RETURN
    CASE
        WHEN @bit = 1 THEN (@bitfieldvalue | @bitmask)
        WHEN @bit = 0 THEN (@bitfieldvalue & ~@bitmask)
        ELSE @bitfieldvalue --NO CHANGE
    END
END

EDIT: Here's some code to test the above function...

declare @bitfield bigint = 0;
print @bitfield;
declare @bitindex int;
set @bitindex = 0;
while @bitindex < 64
begin
  set @bitfield = tutor.dbo.SetBigIntBit(@bitfield,@bitindex,1);
  print @bitfield;  
  set @bitindex = @bitindex + 1;
end
set @bitindex = 0;
while @bitindex < 64
begin
  set @bitfield = tutor.dbo.SetBigIntBit(@bitfield,@bitindex,0);
  print @bitfield;  
  set @bitindex = @bitindex + 1;
end
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文