SSTypesLT 库。三元逻辑类型 SmartBool 的实现





0/5 (0投票)
使用 C# 编程语言实现的三态(三进制)逻辑类型
在编译和运行之前,请阅读代码注释或下方 "使用代码" 一章中关于如何安装必要库的说明。
引言
编程语言中的标准布尔类型只有“true
”和“false
”两种状态。在某些情况下,这还不够。本文简要介绍了 SSTypesLT
库中实现的三元(三状态)布尔类型的开发和实际使用。
动机
三元逻辑是一种除了“True
”和“False
”之外,还有第三个值(例如称为“Undefined
”或“Unknown
”)的逻辑。现代编程语言仅直接支持双状态(二进制)布尔逻辑。这是硬件特性的结果,因为硬件是基于二进制系统运行的。但在某些情况下,三元逻辑对程序员非常有帮助。
数据库管理系统为所有表示数据缺失的数据类型都支持 NULL
值。该值与逻辑上的“Unknown
”具有相同的语义——缺失数据或未知值。作者在配置文件中也遇到了同样的问题,即获取布尔键的缺失值信息并不容易。实际上,三元逻辑扩展了其他数据处理技术的可能性。例如,比较两个 double
值,如果其中一个值为 NAN
,则可以用三元逻辑实现,结果为“Unknown
”。
SSTypesLT 库
本文介绍了 SSTypesLT
库中的 SmartBool
类型,该类型计划在项目 www.GoMap.Az 的数据处理和质量控制中得到广泛应用。作者希望有时间开发 SSTypesLT
库并对其进行描述。作者衷心邀请读者尝试 SSTypesLT
库并提供反馈。
SSTypesLT
库是一个开源项目,位于 此处。SSTypesLT
库是专有的 SSTypes
库的一个开放部分,后者在项目 www.GoMap.Az 中得到了广泛应用。作者感谢
C# 中的可空布尔类型
可空布尔类型可用于在 C# 中实现三元逻辑。语义上,“null
”值对于可空布尔变量完全等同于数据库中的 NULL
值。但这并非旨在支持第三种逻辑状态,因为它是由泛型提供的,泛型适用于任何值类型。可空布尔是这种语言特性在实现三元逻辑中的应用。
因此,这种方法存在一些缺点:
- “
null
”值与“true
”和“false
”相比,具有不同的语义和行为。 - 与可空布尔变量进行操作时,如果值为“
null
”,可能会抛出异常。而“true
”和“false
”的操作则永远不会抛出异常。 - 在某些操作中,可空布尔变量(例如在条件语句中使用)需要显式转换为 bool 类型。
- 可空布尔变量需要额外的内存空间来存储“
hasValue
”标志(参见 struct Nullable <T>, Microsoft Reference Source)。
以下代码片段展示了此类缺点的示例。
bool? bn = null;
bool b2 = (bool)bn;
此代码显示了以下缺点:
- 将变量
bn
的值赋给变量b2
时会抛出异常。 - 将变量
bn
的值赋给变量b2
时需要显式转换为bool
类型(这不能算作缺陷,因为可空布尔比bool
更具信息量)。 - 将“
null
”值(而非“unknown
”或“Undefined
”)赋给变量bn
(语义问题)。
bool? bn = null;
if ((bool)bn)
{
Console.WriteLine("True");
}
else
{
Console.WriteLine("False or Unknown");
}
此代码显示了以下缺点:
- “
if
”语句会抛出异常。 - “
if
”语句需要显式转换为bool
类型。 - 如果
bn
为null
,“False or Unknown
”字符串永远不会显示(语义问题)。 - 将“
null
”值(而非“unknown
”或“undefined
”)赋给变量bn
(语义问题)。
运算符 "&&" 和 "||"
可空布尔未实现“&&
”和“||
”运算符。以下代码将无法编译,因为“&&
”运算符不能应用于可空布尔。
bool? nb1 = null;
bool? nb2 = true;
bool? nbt1 = nb1 && nb2;
bool? nbt2 = nb1 & nb2;
可空布尔类型的扩展
对可空布尔类型的扩展可以提高代码的可读性、语义和行为(参见 Ternary logic.)。但这并不能解决将可空布尔类型用于实现三元逻辑的所有问题。
C# 中的 SmartBool 类型
SmartBool
是 SSTypeLT
库中的一个结构(参见 SSTypes 项目。)。该结构具有允许以对程序员最方便的方式实现三元逻辑的特性。另一个结构 SmartInt
(表示 32 位整数)也采用了类似的方法开发,并且在网站 www.GoMap.Az 中成功使用了数年(参见 Accelerated .NET Types.)。
SmartBool
基于 sbyte
(有符号字节)类型。这种方法允许在不使用跳转语句(“if
”语句)的情况下实现否定运算符“!
”。跳转会降低 CPU 流水线的可预测性,影响性能。不幸的是,在二进制硬件平台上,几乎所有三元逻辑操作都无法通过单个指令实现。SmartBool
的开发目标是内存小且在“if
”语句中速度快。SmartBool
和可空布尔的性能已进行了基准测试和比较(参见下面的“性能”一章)。
SmartBool
的行为与可空布尔的行为略有不同。以下代码在逻辑上与上面提供的代码类似,但行为有所不同。
SmartBool sb = SmartBool.Unknown;
bool sb2 = (bool)sb;
if (sb)
{
Console.WriteLine("True");
}
else
{
Console.WriteLine("False or Unknown");
}
代码的优点是:
- 变量
sb
的值为SmartBool.Unknown
,这在语义上是逻辑值,而不是缺失值的表示。 - 将
sb
的值赋给sb2
时不会抛出异常。 - “
if
”语句语义正确,不会抛出异常。
第二行中的隐式类型转换是必需的,因为 SmartBool
比 bool
更具信息量。
“true”和“false”运算符
Microsoft 的 C# 参考文档描述了“true
”和“false
”运算符(参见 true and false operators (C# Reference).),如下所述。具有已定义的 true 运算符的类型可以是“if
”、“do
”、“while
”和“for
”语句以及条件运算符“?:
”中控制条件表达式的结果类型。
“true
”运算符返回布尔值 true
,表示操作数确定为 true。“false
”运算符返回布尔值 true
,表示操作数确定为 false
。“true
”和“false
”运算符不保证互补。也就是说,对于同一个操作数,“true
”和“false
”运算符都可能返回布尔值 false
。
SmartBool
类型根据此描述实现了“true
”和“false
”运算符。这意味着以下代码将按注释中所述运行。
SmartBool sb1 = ...
SmartBool sb2 = ...
...
if (sb1)
{
// Run if sb1 is True
Console.WriteLine("True");
}
else
{
// Run if sb1 is False or Unknown
Console.WriteLine("False or Unknown");
}
if (!sb2)
{
// Run if sb2 is False
Console.WriteLine("False");
}
else
{
// Run if sb2 is True or Unknown
Console.WriteLine("True or Unknown");
}
因此,对于上述两种情况,对于 Unknown 值,将执行“else
”部分。
运算符 "&&" 和 "||"
SmartBool
实现运算符“&&
”和“||
”。以下代码将毫无问题地编译。
SmartBool sb1 = null;
SmartBool sb2 = true;
SmartBool sbt1 = sb1 && sb2;
SmartBool sbt2 = sb1 & sb2;
相等运算符“==”和“!=”
SmartBool
的相等运算符“==
”和“!=
”返回 SmartBool
类型的结果。而 bool
和可空 Bool
的这些运算符返回 bool
类型的结果。这是 SmartBool
与 bool
和可空 Bool
的重要区别。
将未知值与已知值进行比较的语义可以描述为结果未知的过程。对于长时间计算,如果结果依赖于此输入值,那么输入中的未知值应产生未知结果。SmartBool
支持此语义,但可空布尔不支持。
以下代码展示了这种区别。
bool? nb1 = null;
bool? nb2 = true;
bool? nbt1x = nb1 == nb2;
// nbt1x has false value here
SmartBool sb1 = null;
SmartBool sb2 = true;
SmartBool sbt1x = sb1 == sb2;
// sbt1x has SmartBool.Unknown value here
SmartBool 的真值表
SmartBool
支持三元逻辑的标准真值表。三元逻辑的真值表在许多来源中都有呈现。
NOT 运算(运算符“!
”)
OR 运算(运算符“|
”和“||
”)
AND 运算(运算符“&
”和“&&
”)
相等运算(运算符“==
”)
蕴含运算
异或运算(运算符“^
”)
性能
基准测试表明,对于运算符“!
”、“true
”和“false
”,SmartBool
的性能优于可空布尔。SmartBool
的 OR 运算符“|
”和 AND 运算符“&
”的基准测试显示,与可空布尔相比,性能稍慢。
与可空布尔相比,SmartBool
的相等运算符性能不佳。但这是因为 SmartBool
的相等运算符实现了一个稍微复杂的逻辑并返回 SmartBool
。而可空 Bool
的相等运算符返回 bool
。请参见上面“SmartBool
类型在 C# 中 / 相等运算符“==
”和“!=
””一章。
下表显示了这三种类型的几个操作的基准测试。这些值是相对的,仅用于比较。运算符“true
”和“false
”的值乘以 1000
。该表基于下方表格中的数据构建。
下表展示了赋值运算符的基准测试。如表中所示,将 bool
赋值给 SmartBool
的性能相当低。这是由于运算符逻辑中的几个“if
”检查。同时,将 SmartBool
赋值给 SmartBool
的速度比可空 Bool
赋值给可空 Bool
要快一些。
原始基准测试输出表。请参阅下方“使用代码”章节中链接提供的可下载 zip 压缩包中的代码。
Method | Median | StdDev |
----------------------------- |------------ |------------ |
Asg_Bool2NullableBool | 32.1589 us | 14.2492 us |
Asg_Bool2SmartBool | 137.1827 us | 64.1514 us |
Asg_Bool2Bool | 23.0328 us | 9.5130 us |
Asg_NullableBool2NullableBool | 39.7319 us | 15.7200 us |
Asg_SmartBool2SmartBool | 28.5209 us | 11.4954 us |
NOT_Bool2Bool | 31.4698 us | 8.9368 us |
NOT_NullableBool2NullableBool | 111.9670 us | 38.0665 us |
NOT_SmartBool2SmartBool | 28.3513 us | 9.2716 us |
OR_Bool2Bool | 38.7323 us | 17.7660 us |
OR_NullableBool2NullableBool | 204.6962 us | 76.5525 us |
OR_SmartBool2SmartBool | 254.5754 us | 102.0478 us |
AND_Bool2Bool | 37.4744 us | 11.4081 us |
AND_NullableBool2NullableBool | 170.3016 us | 54.8486 us |
AND_SmartBool2SmartBool | 194.5647 us | 60.3215 us |
EQ_Bool2Bool | 35.4095 us | 12.3996 us |
EQ_NullableBool2NullableBool | 72.4243 us | 22.2527 us |
EQ_SmartBool2SmartBool | 184.6553 us | 56.8425 us |
NEQ_Bool2Bool | 37.7590 us | 12.0692 us |
NEQ_NullableBool2NullableBool | 70.6348 us | 21.5845 us |
NEQ_SmartBool2SmartBool | 154.4584 us | 62.6584 us |
TRUE_Bool | 317.5711 ns | 82.5786 ns |
TRUE_NullableBool | 425.9031 ns | 140.4248 ns |
TRUE_SmartBool | 299.9301 ns | 99.3415 ns |
FALSE_Bool | 312.6404 ns | 102.9978 ns |
FALSE_NullableBool | 494.9714 ns | 133.1724 ns |
FALSE_SmartBool | 294.6255 ns | 67.9278 ns |
测试环境
BenchmarkDotNet =v0.9.7.0
OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Core(TM) i7-3610QM CPU 2.30GHz, ProcessorCount=8
注意:由于散热问题,CPU 频率被强制设置为 1.2 GHz。
摘要
为了简洁起见,可以总结如下结果:
- 可空
Bool
(bool?
) 的设计初衷并非用于实现三元逻辑。 - 对可空
Bool
的扩展可以改进可空Bool
,但不能解决所有问题。 SmartBool
以最佳方式实现了三元逻辑。SmartBool
的逻辑与可空Bool
的逻辑不同。
Using the Code
代码包含示例和基准测试。项目使用 Microsoft Visual Studio Community 2015 创建。两个项目都需要安装 SSTypesLT
库。SmartBoolBenchMark
项目还需要 BenchmarkDotNet
库。
要安装库,您可以使用 Microsoft Visual Studio 中的 NuGet 程序包管理器控制台。可以通过用户菜单“工具/NuGet 程序包管理器/程序包管理器控制台”激活 NuGet 程序包管理器控制台。
要安装 SSTypesLT
库,请输入以下命令并按 **Enter** 键。
Install-Package SSTypesLT
要安装 BenchmarkDotNet
库,请输入以下命令并按 **Enter** 键。
Install-Package BenchmarkDotNet
对于 .NET Framework 4.0 或更高版本,您可以使用版本 0.9.7,通过以下命令安装:
Install-Package BenchmarkDotNet -Version 0.9.7
代码可从本页顶部的链接下载。
历史
- 2019 年 4 月 20 日 - 初稿发布。