问题

C语言 乘以0.01快?还是除以100快?

回答
在C语言中,关于“乘以0.01”和“除以100”哪个更快速,这是一个非常值得探讨的话题,尤其是在追求极致性能的底层开发或者对浮点运算效率敏感的场景下。要回答这个问题,我们需要深入理解计算机如何处理这两种操作,以及它们在硬件层面的具体实现。

理解基础:乘法和除法在计算机中的运算

首先,我们得明白计算机进行运算的基本原理。计算机内部,所有数据最终都是以二进制形式存在的。整数的加减乘除相对直观,而浮点数(如 `float` 和 `double`)则更为复杂,它遵循 IEEE 754 标准,有符号位、指数位和尾数位之分。

在CPU的设计中,通常会有一个专门的单元叫做浮点处理单元(FPU),用于处理浮点数的运算。FPU内部集成了各种算术逻辑单元(ALU),包括加法器、减法器、乘法器、除法器以及更复杂的如平方根、三角函数等单元。

传统上,除法运算比乘法运算在硬件层面要复杂得多,也更耗时。这是因为除法通常需要通过一系列的减法和位移操作来近似计算,过程中涉及更多的迭代和控制逻辑。而乘法,尤其是常数乘法,可以通过更精简的逻辑电路实现,速度更快。

拆解操作:乘以0.01 vs 除以100

现在,我们来具体看看这两个操作:

1. 乘以0.01 (`x 0.01`)
这里的 `0.01` 是一个浮点数常量。
当编译器看到 `x 0.01` 时,它会将其转换为对FPU的调用。FPU会加载 `x` 和 `0.01` 的浮点表示,然后执行乘法指令。
现代CPU的FPU在执行浮点乘法时通常是非常高效的。

2. 除以100 (`x / 100`)
这里的 `100` 是一个整数常量。
然而,当一个浮点数 `x` 与一个整数 `100` 进行除法运算时,C语言会先进行类型提升(或称为隐式类型转换)。`100` 会被转换为一个浮点数(例如 `100.0f` 或 `100.0`,取决于 `x` 的类型是 `float` 还是 `double`),然后再执行浮点除法。
因此,这个操作实际上是 `x / 100.0`。

性能对比:为何乘法通常更快?

基于上述分析,我们可以得出初步结论:

硬件层面: 浮点乘法指令通常比浮点除法指令在CPU上执行得更快。
编译器的优化:
对于 `x 0.01`,编译器知道 `0.01` 是一个常量,它可能会进一步优化。例如,`0.01` 可以被表示成 `1 / 100`,那么 `x 0.01` 就等价于 `x (1 / 100)`。
更厉害的是,编译器可以利用一种叫做倒数乘法(Reciprocal Multiplication)的优化技术。`x / 100` 在数学上等价于 `x (1 / 100)`。而 `1 / 100`,或者说 `0.01`,是一个常量。编译器会预先计算出 `0.01` 的浮点表示。然后,它将除法操作 `x / 100` 转换成一个乘法操作 `x 0.01`。
这种转换是完全合法的,因为 `0.01` 是 `1/100` 的精确(在浮点数精度范围内)表示。编译器在编译时就已经完成了 `1/100` 的计算,生成了 `0.01` 的机器码表示。
因此,对于表达式 `x / 100`,现代编译器(尤其是开启了优化选项,如 GCC 的 `O2` 或 `O3`)会非常智能地将其自动重写成 `x 0.01` 或者一个与之等价的乘法形式。

实际测试和细微差别

理论上,编译器应该已经帮我们做了最优化处理,让 `x / 100` 和 `x 0.01` 在最终生成的机器码层面是等价的。

但是,我们也不能完全排除一些极端的、非常罕见的情况,或者在某些特定架构的CPU上,或者在编译器优化级别很低的情况下,两者之间会存在微小的性能差异。

精度问题(理论上): `0.01` 在二进制浮点数中是一个无限循环小数(类似于十进制中的 `1/3 = 0.333...`)。虽然编译器会使用最接近 `0.01` 的浮点数表示,但这可能会引入极其微小的误差。然而,这种误差通常非常小,对大多数应用来说可以忽略不计,并且通常除法运算本身也会引入类似的误差。所以,精度差异在这里不太可能是性能差异的主要来源。
指令集支持: 现代x86 CPU有专门的浮点乘法(如 `FMUL`)和除法(如 `FDIV`)指令。如果编译器没有进行倒数乘法优化,直接生成除法指令,那么除法会比乘法慢。但如前所述,编译器通常会进行优化。
ARM架构等: 在一些对浮点运算效率要求极高的嵌入式系统或特定架构的CPU上,除法器的设计可能与x86有较大差异,但基本原理——乘法通常比除法简单且快速——依然成立。

结论:

在绝大多数现代C语言开发场景下,编译器会非常智能地将 `x / 100` 优化为 `x 0.01`(或等效的乘法形式)。因此,从实际运行效率上看,两者应该几乎没有差别。

如果你非要一个明确的答案,或者是在一个非常严苛的性能测试环境中进行比较,那么:

如果编译器开启了充分的优化(推荐使用 `O2` 或 `O3`),`x / 100` 和 `x 0.01` 在执行速度上会是等价的。
理论上,直接使用 `x 0.01` 更加直接地表达了乘法操作,并且依赖于 FPU 的最高效乘法指令。而 `x / 100` 则依赖于编译器能否成功地将其转换为一个高效的乘法。

实际编程建议:

1. 可读性第一: 除非你确实是在性能瓶颈的地方进行微观优化,否则选择更清晰、更易于理解的代码。如果你想表达“缩小到百分之一”,那么 `x / 100.0` 可能比 `x 0.01` 更直观。
2. 信赖编译器: 让编译器做它的工作。现代编译器的优化能力非常强大,它们通常能将你写的代码转化为最有效的机器码。
3. 性能分析: 如果你真的怀疑某段代码的性能,最好的方法是使用性能剖析工具(Profiler)来测量实际的运行时间,而不是凭空猜测。

总而言之,别太纠结于这个问题。在大多数情况下,让编译器帮你选择最快的路径。如果一定要选,直接使用 `x 0.01` 是最不容易出错且理论上最直接高效的写法,因为它直接指示了乘法运算,并且编译器可以利用 `0.01` 的预计算值。而 `x / 100` 的效率则“隐藏”在编译器的优化能力之下。

网友意见

user avatar

整理并且统一回复一下评论里的一些观点:

其实题主问的一个核心问题是,等效的乘法和除法,哪个更快。这里问题有一个前提:如果是数学上的等效的乘除法,必然有一个是浮点计算,所以我的回答的结论其实是正确的:浮点操作几乎在任何时候都比整数操作要慢

注意这里的几乎,例外情况包括:流水线情况、cache的情况、编译器优化、代码如何编写方式等等。

评论里有人质疑latency是否能完全代表性能,latency肯定不能代表实际性能,但已经没有更好的尺度来衡量指令的性能了,所以latency几乎是除了真机实验以外的最好的尺子。

同时我的回答里,另一个观点也是很重要的:浮点乘除本身的性能跟整数差别不大,浮点操作会涉及到整数与浮点之间的转换,这里的指令开销要远远大于乘除计算本身

找一份Intel或者AMD的相关手册可以查到指令集的性能,一个大概的结论就是,从乘除指令的层面上看,按指令开销从小到大是:整乘 < 浮点乘 < 浮点除 < 整除

单个指令,整除是最慢的,但多数情况下,立即数的除法会被优化掉(注:IDIV的latency在20-27之间,多数编译器都会优化成其他算法),只有两个变量做除法的时候,才有可能出现最糟糕的性能。所以如果从整体考虑,等效乘除,就意味着必然有整数与浮点之间的互转,这里通常需要额外5-6条浮点指令,开销远远大于整数除法。

所以就有了最终结论,浮点要慢于整数,开销大头不在乘除本身。

---------------原回答---------------

先说结论,默认编译条件下,整数除法比浮点乘法要快。然而,开销的大头并不在乘除本身。

反汇编代码如下:

           int k = y * 0.01;  614:   8b 45 dc                mov    -0x24(%ebp),%eax  617:   89 45 d0                mov    %eax,-0x30(%ebp)  61a:   db 45 d0                fildl  -0x30(%ebp)  61d:   dd 83 70 e7 ff ff       fldl   -0x1890(%ebx)  623:   de c9                   fmulp  %st,%st(1)  625:   d9 7d d6                fnstcw -0x2a(%ebp)  628:   0f b7 45 d6             movzwl -0x2a(%ebp),%eax  62c:   80 cc 0c                or     $0xc,%ah  62f:   66 89 45 d4             mov    %ax,-0x2c(%ebp)  633:   d9 6d d4                fldcw  -0x2c(%ebp)  636:   db 5d e0                fistpl -0x20(%ebp)  639:   d9 6d d6                fldcw  -0x2a(%ebp)     int m = y / 100;  63c:   8b 4d dc                mov    -0x24(%ebp),%ecx  63f:   ba 1f 85 eb 51          mov    $0x51eb851f,%edx  644:   89 c8                   mov    %ecx,%eax  646:   f7 ea                   imul   %edx  648:   c1 fa 05                sar    $0x5,%edx  64b:   89 c8                   mov    %ecx,%eax  64d:   c1 f8 1f                sar    $0x1f,%eax  650:   29 c2                   sub    %eax,%edx  652:   89 d0                   mov    %edx,%eax  654:   89 45 e4                mov    %eax,-0x1c(%ebp     

浮点乘法用的是fmulp,整数除法用的是imul,在Intel Haswell平台上fmulp的Latency是5,imul的Latency是3,虽然有差距,但也不是差距特别巨大的,反倒是其它浮点指令,比如fild的Latency是6,而整数的mov指令,实际可能都不需要一个cycle就完成。所以最终的整数计算的速度可能是浮点数的几倍。

把Latency标注出来如下(注:不一定代表真实效率,但关联度很大):

浮点:

        614:   8b 45 dc                mov    -0x24(%ebp),%eax     ### Latency 3  617:   89 45 d0                mov    %eax,-0x30(%ebp)     ### Latency 2 * 0.5  61a:   db 45 d0                fildl  -0x30(%ebp)          ### Latency 6  61d:   dd 83 70 e7 ff ff       fldl   -0x1890(%ebx)        ### Latency 3 * 0.5  623:   de c9                   fmulp  %st,%st(1)           ### Latency 5  625:   d9 7d d6                fnstcw -0x2a(%ebp)          ### Latency 6  628:   0f b7 45 d6             movzwl -0x2a(%ebp),%eax     ### Latency 3 * 0.5  62c:   80 cc 0c                or     $0xc,%ah             ### Latency 1 * 0.25  62f:   66 89 45 d4             mov    %ax,-0x2c(%ebp)      ### Latency 2 * 0.5  633:   d9 6d d4                fldcw  -0x2c(%ebp)          ### Latency 7  636:   db 5d e0                fistpl -0x20(%ebp)          ### Latency 6  639:   d9 6d d6                fldcw  -0x2a(%ebp)          ### Latency 7     

整数:

        63c:   8b 4d dc                mov    -0x24(%ebp),%ecx     ### Latency 3  63f:   ba 1f 85 eb 51          mov    $0x51eb851f,%edx     ### Latency 1 * 0.25  644:   89 c8                   mov    %ecx,%eax            ### Latency 1 * 0.25  646:   f7 ea                   imul   %edx                 ### Latency 3  648:   c1 fa 05                sar    $0x5,%edx            ### Latency 1 * 0.5  64b:   89 c8                   mov    %ecx,%eax            ### Latency 1 * 0.25  64d:   c1 f8 1f                sar    $0x1f,%eax           ### Latency 1 * 0.5  650:   29 c2                   sub    %eax,%edx            ### Latency 1 * 0.5  652:   89 d0                   mov    %edx,%eax            ### Latency 1 * 0.25  654:   89 45 e4                mov    %eax,-0x1c(%ebp)     ### Latency 2 * 0.5     

对比一下,就可以看出来浮点数的性能比整数要差得多。


统一回复一下评论的疑问:

y是int,从键盘输入

       #include <stdio.h> int main() {     int y;     scanf("%d", &y);     int k = y * 0.01;     int m = y / 100;     printf("%d,%d
", k, m);     return 0; }     

latency数据来源于第三方手册

user avatar

乘以0.01和除以100哪个快?

在知乎上看到这个问题,觉得挺有趣的。下面的回答五花八门,但是就是没有直接给出真正的benchmark结果的。还有直接搬反汇编代码的,只不过汇编里用了 x87 FPU 指令集,天那这都 202x 还有程序用这个老掉牙的浮点运算指令集的吗?

我也决定研究一下,读反汇编,写benchmark。平台以 x86-64 为准,编译器 clang 13,开编译器优化(不开优化谈速度无意义)

代码及反汇编

gcc.godbolt.org/z/rvT9n

首先讨论整数的情况,首先是整数除100

       int int_div(int num) {     return num / 100; }     

结果为

       int_div(int):                            # @int_div(int)         movsxd  rax, edi         imul    rax, rax, 1374389535         mov     rcx, rax         shr     rcx, 63         sar     rax, 37         add     eax, ecx         ret     

稍作解释。edi为函数的第一个整数参数(x64调用约定);imul为有符号整数乘法;shr为逻辑右移(符号位补0);sar为算数右移(符号位不变)

可以看到编译器将除法编译为乘法和位运算,意味着编译器认为这么一大串指令也比除法指令快。代码里一会算术右移一会逻辑右移是为了兼容负数。如果指定为无符号数,结果会简单一些

       unsigned int_div_unsigned(unsigned num) {     return num / 100; }     

结果为

       int_div_unsigned(unsigned int):                  # @int_div_unsigned(unsigned int)         mov     eax, edi         imul    rax, rax, 1374389535         shr     rax, 37         ret     

也可以强制让编译器生成除法指令,使用volatile大法

       int int_div_force(int num) {     volatile int den = 100;     return num / den; }     

结果为

       int_div_force(int):                     # @int_div_force(int)         mov     eax, edi         mov     dword ptr [rsp - 4], 100         cdq         idiv    dword ptr [rsp - 4]         ret     

稍作解释,edi是第一个整数参数(x64调用约定),cdq是有符号32位至64位整数转化,idiv是有符号整数除法。 整数除法指令使用比较复杂。首先操作数不能是立即数。然后如果除数是32位,被除数必须被转化为64位,cdq指令是在做这个转化(因为有符号位填充的问题)。另外汇编里出现了内存操作,这是volatile的负作用,会对结果有些影响。

下面是整数乘0.01

       int int_mul(int num) {     return num * 0.01; }     

结果为

       .LCPI3_0:         .quad   0x3f847ae147ae147b              # double 0.01 int_mul(int):                            # @int_mul(int)         cvtsi2sd        xmm0, edi         mulsd   xmm0, qword ptr [rip + .LCPI3_0]         cvttsd2si       eax, xmm0         ret     

稍作解释,edi是第一个整数参数(x64调用约定),cvtsi2sd是整数到双精度浮点数转换(ConVerT Single Integer TO Single Double),mulsd是双精度整数乘法,cvttsd2si是双精度浮点数到整数转换(截断小数部分),xmm0为SSE2的32位寄存器 因为没有整数和浮点数运算的指令,实际运算中会先将整数转换为浮点数,运算完毕后还要转回来。计算机中整数和浮点数存储方法不同,整数就是简单的补码,浮点数是IEEE754的科学计数法表示,这个转换并不是简单的位数补充。

下面讨论浮点数的情况。首先是浮点数除100

       double double_div(double num) {     return num / 100; }     

结果为

       .LCPI4_0:         .quad   0x4059000000000000              # double 100 double_div(double):                        # @double_div(double)         divsd   xmm0, qword ptr [rip + .LCPI4_0]         ret     

稍作解释,xmm0是第一个浮点数参数(x64调用约定),divsd是SSE2双精度浮点数除法。因为SSE寄存器不能直接mov赋值立即数,立即数的操作数都是先放在内存中的,即qword ptr [rip + .LCPI4_0]

然后是浮点数乘0.01

       double double_mul(double num) {     return num * 0.01; }     

结果为

       .LCPI5_0:         .quad   0x3f847ae147ae147b              # double 0.01 double_mul(double):                        # @double_mul(double)         mulsd   xmm0, qword ptr [rip + .LCPI5_0]         ret     

结果与除法非常接近,都只有一个指令,不需要解释了

Benchmark

quick-bench.com/q/1rmqh

结果为(按照用时从小到大排序)

  1. 浮点数乘
  2. 无符号整数除(编译为乘法和移位)
  3. 有符号整数除(编译为乘法和移位)
  4. 整数乘(用时与编译为乘法的整数除十分接近)
  5. 强制整数除
  6. 浮点数除

其中用时最多的浮点数除和浮点数乘用时相差14倍。另外值得一提的是,如果启用 --ffast-math 编译参数,编译器会把浮点数除编译为浮点数乘

类似的话题

  • 回答
    在C语言中,关于“乘以0.01”和“除以100”哪个更快速,这是一个非常值得探讨的话题,尤其是在追求极致性能的底层开发或者对浮点运算效率敏感的场景下。要回答这个问题,我们需要深入理解计算机如何处理这两种操作,以及它们在硬件层面的具体实现。理解基础:乘法和除法在计算机中的运算首先,我们得明白计算机进行.............
  • 回答
    C 语言的设计理念是简洁、高效、接近硬件,而其对数组的设计也遵循了这一理念。从现代编程语言的角度来看,C 语言的数组确实存在一些“不改进”的地方,但这些“不改进”很大程度上是为了保持其核心特性的兼容性和效率。下面我将详细阐述 C 语言为何不“改进”数组,以及这种设计背后的权衡和原因:1. 数组在 C.............
  • 回答
    C 语言王者归来,原因何在?C 语言,这个在编程界已经沉浮数十载的老将,似乎并没有随着时间的推移而消逝,反而以一种“王者归来”的姿态,在许多领域焕发新生。它的生命力如此顽强,甚至在 Python、Java、Go 等语言层出不穷的今天,依然占据着不可动摇的地位。那么,C 语言究竟为何能实现“王者归来”.............
  • 回答
    C语言指针是否难,以及数学大V认为指针比范畴论还难的说法,是一个非常有趣且值得深入探讨的话题。下面我将尽量详细地阐述我的看法。 C语言指针:理解的“门槛”与“终点”首先,我们需要明确“难”的定义。在编程领域,“难”通常指的是: 学习曲线陡峭: 需要花费大量时间和精力去理解和掌握。 容易出错:.............
  • 回答
    C 语言中的 `void main()` 并非是语言标准规定的写法,它的出现和流传,更像是一个历史遗留问题、编译器兼容性以及开发者习惯共同作用的结果。要详细讲解,我们需要从 C 语言的诞生和演变说起。1. C 语言的起源和早期标准 (K&R C) C 语言的诞生: C 语言最初是由 Dennis.............
  • 回答
    C语言自学能到什么高度?详细解析C语言,作为一门强大且经典的编程语言,其学习曲线相对陡峭,但一旦掌握,其应用范围之广,性能之优越,是许多其他语言难以比拟的。 仅凭自学,C语言可以让你达到一个非常高的技术高度,足以让你在许多领域成为一名优秀的开发者甚至专家。以下将从多个维度详细阐述C语言自学所能达到的.............
  • 回答
    在 C 语言中判断一个数列是否为等差数列,核心思想是验证数列中任意相邻两项的差值是否恒定不变。下面我将从概念、算法实现、注意事项以及代码示例等方面进行详细讲解。 一、什么是等差数列?在数学中,等差数列(Arithmetic Progression 或 Arithmetic Sequence)是指一个.............
  • 回答
    在 C 语言中,不用 `goto` 和多处 `return` 进行错误处理,通常依靠以下几种模式和技术。这些方法旨在提高代码的可读性、可维护性,并遵循更结构化的编程原则。核心思想: 将错误处理的逻辑集中到函数退出前的某个点,或者通过特定的返回值来指示错误。 1. 集中错误处理(Single Exit.............
  • 回答
    这个问题很有意思,也触及到了C语言作为一种基础性语言的根本。很多人听到“C语言本身是用什么写的”时,会先想到“用更高级的语言写的”,比如Python或者Java。但事实并非如此,或者说,这个答案需要更深入的理解。首先,我们需要明确一点:C语言最初的实现,也就是早期的C编译器,并不是用C语言本身写的。.............
  • 回答
    C 语言中,一些自带函数返回的是指向数组的指针,而你无需手动释放这些内存。这背后涉及到 C 语言的内存管理机制以及函数设计哲学。要弄清楚这个问题,我们需要从几个关键点入手: 1. 返回指针的函数,内存的归属至关重要首先,理解函数返回指针时,内存的“所有权”是谁的,是解决这个疑问的核心。当一个函数返回.............
  • 回答
    在 C 语言中,枚举(`enum`)是一种用户定义的数据类型,它允许你为一组整数常量命名。这使得代码更具可读性和可维护性。而枚举中的 `end` 关键字,严格来说,它本身并不是 C 语言标准枚举定义的一部分,而是一种常见的编程约定或模式,用于标记枚举序列的结束。让我来详细解释一下,并尽可能剥离 AI.............
  • 回答
    在C语言中,严格来说,不能直接“判断”一个变量的类型是否是`int`或`float`。C语言是一种静态类型语言,变量的类型在编译时就已经确定,并且不能在运行时随意更改或检查。当你声明一个变量时,你就已经告诉了编译器它的类型。不过,如果你想表达的是“根据当前存储的值,推断出这个变量应该被视为整数还是浮.............
  • 回答
    在 C 语言中,让不同线程之间能够交流信息、协同工作,这本身就是多线程编程中最核心也是最需要仔细处理的部分。别把它想得太玄乎,无非就是大家共享一块内存,然后约定好怎么读写这块内存罢了。只不过,这“约定”怎么立得住,不让大家互相捣乱,才是关键。咱们把线程通信这事儿,拆解成几个层面来说。 1. 共享内存.............
  • 回答
    在C语言中, `a > b ? a < c ? a : b : c` 这种写法是利用了三元运算符 (?:) 的嵌套。它是一种简洁的条件表达式,用来根据条件的真假返回不同的值。理解它的关键在于一步步拆解它的逻辑。咱们就来好好捋一捋这串表达式的判断过程,讲得透彻一些,保证让你明白它到底是怎么回事儿。首先.............
  • 回答
    C 语言里,一旦你用了 ` ` 来进行换行,确实就“回不去了”——至少在标准的输出流中是这样。这背后的原理,要从计算机如何处理文本输出和终端(或者说显示器)的工作方式说起。核心点:文本流与终端的坐标系统想象一下你的程序输出的文本,就像一条源源不断地向前流动的河流。` `(换行符)就是这条河流中的一个.............
  • 回答
    朋友,咱们这话题聊得挺实在的。C语言现在还有没有“必要”学,未来还有没有“用”,这绝对是个值得深入掰扯掰扯的问题。别听那些虚头巴脑的,咱就从实际出发,好好说说。C语言现在还有没有“必要”学?我想说,如果你想在计算机底层或者和效率打交道,那 C 语言的“必要性”依然挺强的,甚至可以说是基石性的。你得明.............
  • 回答
    在 C 语言编程的世界里,选择一个趁手的编辑器就像是给了你一对飞翔的翅膀。这不仅关乎效率,更影响着你的开发体验和创造力。市面上的编辑器琳琅满目,各有千秋,要说哪个“最好用”,这其实是个非常主观的问题,取决于你的个人习惯、项目需求以及你追求的侧重点。不过,如果你想在众多选择中找到最适合你的那位,不妨先.............
  • 回答
    嘿,哥们,聊到 C 语言的“奇技淫巧”,这可就有意思了。这东西,说白了就是利用 C 语言一些不太直观,但又特别巧妙的特性,来达成一些别人想不到或者达不到的效果。很多时候,这些技巧能让你写出更精炼、更高效的代码,当然了,用不好也容易把自己绕进去。我这里给你掰扯几个比较典型的,保证不像是那种写流水账的A.............
  • 回答
    这个问题在 C 语言中,关于表达式 `a = a++;` 的结果,是初学者乃至一些有经验的程序员都可能感到困惑的地方。它的行为并非我们直观想象的那么简单,并且会涉及到 C 语言中一个非常重要的概念:未定义行为(Undefined Behavior)。首先,让我们尝试理解一下 C 语言是如何处理这个表.............
  • 回答
    在 C 语言的世界里,指针是必不可少的工具,它们就像是内存地址的“指示牌”,让我们能够更灵活地操作数据。而当我们将指针与数组、函数结合起来时,就诞生了一系列强大而又容易让人困惑的概念:指针数组、数组指针、函数指针,以及指向函数的指针。别担心,今天我们就来把它们掰开了揉碎了,让你彻底搞懂它们到底是怎么.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有