Hybrid A*示例代码KTH的path_planner分析

2023-05-16

Hybrid A star Algorithm Analysis

updating…

1. Data Flow

in main.cpp

    // init ros node
    ros::init(argc, argv, "a_star");

    // main function
    HybridAStar::Planner hy;
    hy.plan();  // main process

    ros::spin();
    return 0;

there are two main parts,

  • HybridAStar::Planner, use constructor function to initialize an instance of planner
  • HybridAStar::Planner::plan(), include each sub-part of the main process

1.1 Constructor function

firstly, publish a topic named /move_base_simple/start, from ROS WIKI.

message type is geometry_msgs::PoseStamped, including 3-D coordinates, Points
and pose orientation, Quaternion.
And will use a function to define value of this message.

then define several subscribers, including

  • /map, call function Planner::setMap
  • /move_base_simple/goal, call function Planner::setGoal
  • /initialpose, call function Planner::setStart

publish a topic for start point, subscribe topics for start point, goal and map.

1.1.1 set map

    grid = map;  // grid: nav_msgs::OccupancyGrid::Ptr
    //update the configuration space with the current map
    configurationSpace.updateGrid(map);

grid is an instance of nav_msgs::OccupancyGrid::Ptr, a pointer in navigation message package.

configurationSpace is an instance of class CollisionDetection; updating grid is rather simple.

  /*!
     \brief updates the grid with the world map
  */
  void updateGrid(nav_msgs::OccupancyGrid::Ptr map) {grid = map;}

but it seems no difference with line above, why?

    int height = map->info.height;
    int width = map->info.width;
    bool** binMap;
    binMap = new bool*[width];

binMap, a pointer to a bool pointer, binMap = new bool*[width],
using new to alloc memory for this, type is bool*, a bool type pointer,
with number of width.

这一步创建了一个指向指针的指针,并且使用new进行内存分配,分配了width个类型为bool*的变量。

    for (int x = 0; x < width; x++){
        binMap[x] = new bool[height];
    }

now binMap is a 2 dimension pointer array with range of width * height.

    for (int x = 0; x < width; ++x) {
        for (int y = 0; y < height; ++y) {
            binMap[x][y] = map->data[y * width + x] ? true : false;
        }
    }

using a ?: operator, can be simplified as xxx != 0;

  • if map->data[y * width + x] = true, binMap[x][y] = true
  • if map->data[y * width + x] = false, binMap[x][y] = false
    voronoiDiagram.initializeMap(width, height, binMap);
    voronoiDiagram.update();
    voronoiDiagram.visualize();

some voronoi diagram stuff, ignore here. binMap is used in voronoi diagram.

1.1.2 set goal

    // retrieving goal position
    float x = end->pose.position.x / Constants::cellSize;
    float y = end->pose.position.y / Constants::cellSize;
    float t = tf::getYaw(end->pose.orientation);

    std::cout << "I am seeing a new goal x:" << x << " y:" << y << " t:" << Helper::toDeg(t) << std::endl;

    if (grid->info.height >= y && y >= 0 && grid->info.width >= x && x >= 0) {
        validGoal = true;
        goal = *end;

        if (Constants::manual){
            plan();
        }
    } 

why call plan() here?

1.1.3 set start

void Planner::setStart(const geometry_msgs::PoseWithCovarianceStamped::ConstPtr& initial) {
    float x = initial->pose.pose.position.x / Constants::cellSize;
    float y = initial->pose.pose.position.y / Constants::cellSize;
    float t = tf::getYaw(initial->pose.pose.orientation);
}

set coordinates of start.

    geometry_msgs::PoseStamped startN;
    startN.pose.position = initial->pose.pose.position;
    startN.pose.orientation = initial->pose.pose.orientation;
    startN.header.frame_id = "map";
    startN.header.stamp = ros::Time::now();

    if (grid->info.height >= y && y >= 0 && grid->info.width >= x && x >= 0) {
        validStart = true;
        start = *initial;
    
        if (Constants::manual) { plan();}
    
        // publish start for RViz
        pubStart.publish(startN);
    }

if start is valid, call plan() here, and publish start information to topic "/move_base_simple/start".

but I’m confused why plan() need to be called here, even it will be called in the next step in main?

before the program running into ros::spin(), message of start and goal is sent by publisher,

then the program goes into plan()

1.2 Planner::plan()

firstly, define list pointers and initialize lists

        int width = grid->info.width;
        int height = grid->info.height;
        int depth = Constants::headings;
        int length = width * height * depth;
        // define list pointers and initialize lists
        Node3D* nodes3D = new Node3D[length]();
        Node2D* nodes2D = new Node2D[width * height]();

then define the goal point

        x = start.pose.pose.position.x / Constants::cellSize;
        y = start.pose.pose.position.y / Constants::cellSize;
        t = tf::getYaw(start.pose.pose.orientation);

the planning will start

        // CLEAR THE VISUALIZATION
        visualization.clear();
        // CLEAR THE PATH
        path.clear();
        smoothedPath.clear();
        // FIND THE PATH
        Node3D* nSolution = Algorithm::hybridAStar(
                nStart, nGoal, nodes3D, nodes2D, width, height,
                configurationSpace, dubinsLookup, visualization);
        // TRACE THE PATH
        smoother.tracePath(nSolution);
        // CREATE THE UPDATED PATH
        path.updatePath(smoother.getPath());
        // SMOOTH THE PATH
        smoother.smoothPath(voronoiDiagram);
        // CREATE THE UPDATED PATH
        smoothedPath.updatePath(smoother.getPath());
  • main part, get an initial path by Algorithm::hybridAStar
  • send path to smoother, smoother.tracePath(nSolution)
  • send path to path
  • smooth path, smoother.smoothPath(voronoiDiagram);
  • update smoothed path

and the rest will be ROS topic and service stuff.

        // PUBLISH THE RESULTS OF THE SEARCH
        path.publishPath();
        path.publishPathNodes();
        path.publishPathVehicles();
        smoothedPath.publishPath();
        smoothedPath.publishPathNodes();
        smoothedPath.publishPathVehicles();
        visualization.publishNode3DCosts(nodes3D, width, height, depth);
        visualization.publishNode2DCosts(nodes2D, width, height);

        // delete lists
        delete [] nodes3D;
        delete [] nodes2D;

but I’m still confused about the data transferring between ROS-rviz and this program.

Providing a ROS graph here,
在这里插入图片描述

Summarise some questions:

  • what is the different between initialpose and move_base_simple/start?
  • in which way the map image is delivered?
  • what’s the function of tf in this ROS program?

Map is sent to ROS master when running the manual.launch script, through the map_server node.
In detail, by loading a yaml file to choose map file, namely a jpg file.

According to the simulation process, user can add start and goal in rviz.
Meanwhile, data will be transferred to this program, through ROS master and node, but which node?

1.3 Overall Analysis

我为什么又写了这么一节?因为耽搁了几天回来再看的时候,我忘了我写到哪了,就记得整体的数据流还没分析清楚。
上面的1.1, 1.2两个小节也提出了一些问题,在此做一个整汇总。

So in main.cpp, firstly create an instance of Planner class.
As we know about c++ feature, this will lead to the call of constructor function by this class.

According to analysis above, we know that in the constructor function, several publisher and subscriber are defined.

  • publisher: “/move_base_simple/start”
  • subscriber: “/map”, callback function is setMap
  • subscriber: “/move_base_simple/goal”, callback function is setGoal
  • subscriber: “/initialpose”, callback function is setStart

As stated by ROS WIKI, only when the program reaches ros::spin() can the
subscribers start to receive message and callback their functions.
So process above only name some variables.

Then the program goes to plan().
However, without valid start and goal point, this will not be proceeded.

Now it’s time for ros::spin(), if user provide start and goal information to RVIZ,
the message will be received by those subscribers, and callback functions will be called.

In callback functions setStart and setGoal, plan() shall be called again and again
once when the start or goal is set or changed.

Until now, data flow is basically clear.

2. Main Functions

2.1 ROS and OMPL interface

2.2 Map data structure and usage

2.3 Dynamic voronoi diagram construction

this part will not be analyzed temporarily.

2.4 Hybrid A star algorithm

This part is in class algorithm.cpp and algorithm.h.

Got 4 functions(one class member) and a struct.

  • float aStar
  • void updateH
  • Node3D* dubinsShot
  • Node3D* Algorithm::hybridAStar
  • struct CompareNodes.

2.4.1 Node for 3D and 2D

The Node class by author is defined to describe each node of hybrid A star node,
just like A star node with information of x,y,g,f.

The difference between 2D and 3D is whether to take θ \theta θ into consideration.

In Node2D.h and cpp, there are several functions:

  • isOnGrid, if this node is inside the map
  • createSuccessor(const int i), set the next node according to i, which has 8 options.
  • bool Node2D::operator == (const Node2D& rhs) const, return if the same position
  • setIdx, set id in a one dimension array

In Node3D.h and Node3D.cpp, there are more functions:

  • isOnGrid, x and y is inside the map, and heading angle is less than 72 degree(defined by the author)
  • isInRange, if is in the range of test Dubins shot
  • createSuccessor
// R = 6, 6.75 DEG
const float Node3D::dy[] = { 0,        -0.0415893,  0.0415893};
const float Node3D::dx[] = { 0.7068582,   0.705224,   0.705224};
const float Node3D::dt[] = { 0,         0.1178097,   -0.1178097};

在这里插入图片描述

bool Node3D::isInRange(const Node3D& goal) const {
    int random = rand() % 10 + 1;
    // rand(), Return a random integer between 0 and RAND_MAX inclusive
    // #define	RAND_MAX	2147483647
    float dx = std::abs(x - goal.x) / random;
    float dy = std::abs(y - goal.y) / random;
    return (dx * dx) + (dy * dy) < Constants::dubinsShotDistance;
}

random?

Node3D* Node3D::createSuccessor(const int i) {
    float xSucc;
    float ySucc;
    float tSucc;

    // calculate successor positions forward
    if (i < 3) {
        xSucc = x + dx[i] * cos(t) - dy[i] * sin(t);
        ySucc = y + dx[i] * sin(t) + dy[i] * cos(t);
        tSucc = Helper::normalizeHeadingRad(t + dt[i]);
    }
    // backwards
    else {
        xSucc = x - dx[i - 3] * cos(t) - dy[i - 3] * sin(t);
        ySucc = y - dx[i - 3] * sin(t) + dy[i - 3] * cos(t);
        tSucc = Helper::normalizeHeadingRad(t - dt[i - 3]);
    }

    return new Node3D(xSucc, ySucc, tSucc, g, 0, this, i);
}

在这里插入图片描述

static inline float normalizeHeadingRad(float t) {
  if (t < 0) {
    t = t - 2.f * M_PI * (int)(t / (2.f * M_PI));
    return 2.f * M_PI + t;
  }

  return t - 2.f * M_PI * (int)(t / (2.f * M_PI));
}

just a normalize function if t < 0 t<0 t<0;

void Node3D::updateG() {
    // forward driving
    if (prim < 3) {
        // if i < 3
        // penalize turning
        // if the direction changed from last motion
        if (pred->prim != prim) {
            // penalize change of direction, from backward to forward
            if (pred->prim > 2) {
                // penaltyTurning = 10.5, penaltyCOD = 2.0
                g += dx[0] * Constants::penaltyTurning * Constants::penaltyCOD;
            } else {
                // if still forward, just a minor direction changing
                // penaltyTurning = 10.5
                g += dx[0] * Constants::penaltyTurning;
            }
        } else {
            g += dx[0];
        }
    }
    // reverse driving
    else {
        // penalize turning and reversing
        if (pred->prim != prim) {
            // penalize change of direction
            if (pred->prim < 3) {
                g += dx[0] * Constants::penaltyTurning * Constants::penaltyReversing * Constants::penaltyCOD;
            } else {
                g += dx[0] * Constants::penaltyTurning * Constants::penaltyReversing;
            }
        } else {
            g += dx[0] * Constants::penaltyReversing;
        }
    }

}

for updateG function, take forward motion as an example,

  • if the motion of last step is the same as this one, g = g + d x [ 0 ] g=g+dx[0] g=g+dx[0]
  • else, will add other penalty
    • if the two motion is both forward, add a slight penalty
    • else, add a heavier penalty

I doubt about whether this method can satisfy dynamic constraint.

2.4.2 Dubins

dubinsShot seems to be the one out of some complex data structure, so let’s analyse it secondly.

Node3D* dubinsShot(Node3D& start, const Node3D& goal, CollisionDetection& configurationSpace) {
    // start
    double q0[] = { start.getX(), start.getY(), start.getT() };
    // goal
    double q1[] = { goal.getX(), goal.getY(), goal.getT() };
    // initialize the path
    DubinsPath path;
    // calculate the path
    dubins_init(q0, q1, Constants::r, &path);

    int i = 0;
    float x = 0.f;
    float length = dubins_path_length(&path);

    Node3D* dubinsNodes = new Node3D [(int)(length / Constants::dubinsStepSize) + 1];

    // avoid duplicate waypoint
    x += Constants::dubinsStepSize;
    while (x <  length) {
        double q[3];
        dubins_path_sample(&path, x, q);
        dubinsNodes[i].setX(q[0]);
        dubinsNodes[i].setY(q[1]);
        dubinsNodes[i].setT(Helper::normalizeHeadingRad(q[2]));

        // collision check
        if (configurationSpace.isTraversable(&dubinsNodes[i])) {
            // set the predecessor to the previous step
            if (i > 0) {
                dubinsNodes[i].setPred(&dubinsNodes[i - 1]);
            } else {
                dubinsNodes[i].setPred(&start);
            }

            if (&dubinsNodes[i] == dubinsNodes[i].getPred()) {
                std::cout << "looping shot";
            }

            x += Constants::dubinsStepSize;
            i++;
        } else {
            //      std::cout << "Dubins shot collided, discarding the path" << "\n";
            // delete all nodes
            delete [] dubinsNodes;
            return nullptr;
        }
    }

    //  std::cout << "Dubins shot connected, returning the path" << "\n";
    return &dubinsNodes[i - 1];
}

in the dubins.cpp, there is a detailed method.

2.4.3 Conventional A star

Let’s look back at conventional a star,

在这里插入图片描述

and they are basically the same. though the name of variables of this code is a little uncomfortable to an OCD patient like me.

2.4.4 Update H

we know that f = g + h f=g+h f=g+h, and g g g is related to the node’s predecessor.

h h h is associated with the position from node to goal, for conventional A star this can be x 2 + y 2 \sqrt{x^2+y^2} x2+y2 . However, if θ \theta θ is being considered, this f f f could be more complicated, so the author has a function in algorithm.cpp to illustrate it.

one is to use Reed and Sheep curves to get cost,

    if (Constants::reverse && !Constants::dubins) {
        ompl::base::ReedsSheppStateSpace reedsSheppPath(Constants::r);
        State* rsStart = (State*)reedsSheppPath.allocState();  // ompl::base::SE2StateSpace::StateType State
        State* rsEnd = (State*)reedsSheppPath.allocState();
        rsStart->setXY(start.getX(), start.getY());
        rsStart->setYaw(start.getT());
        rsEnd->setXY(goal.getX(), goal.getY());
        rsEnd->setYaw(goal.getT());
        reedsSheppCost = reedsSheppPath.distance(rsStart, rsEnd);
    }

remind that the start and goal denote two points in space, not the exactly start and goal position.

the second is 2-D heuristic,

    // if twoD heuristic is activated determine the shortest path
    // unconstrained with obstacles
    if (Constants::twoD && !nodes2D[(int)start.getY() * width + (int)start.getX()].isDiscovered()) {
        //    ros::Time t0 = ros::Time::now();
        // create a 2d start node
        Node2D start2d(start.getX(), start.getY(), 0, 0, nullptr);
        // create a 2d goal node
        Node2D goal2d(goal.getX(), goal.getY(), 0, 0, nullptr);
        // run 2d astar and return the cost of the cheapest path for that node
        nodes2D[(int)start.getY() * width + (int)start.getX()].setG(aStar(goal2d,
                                                                          start2d, nodes2D,
                                                                          width, height,
                                                                          configurationSpace,
                                                                          visualization));
    }

    if (Constants::twoD) {
        // offset for same node in cell
        twoDoffset = sqrt(((start.getX() - (long)start.getX()) - (goal.getX() - (long)goal.getX())) * ((start.getX() -
                          (long)start.getX()) - (goal.getX() - (long)goal.getX())) +
                          ((start.getY() - (long)start.getY()) - (goal.getY() -
                          (long)goal.getY())) * ((start.getY() - (long)start.getY()) - (goal.getY() - (long)goal.getY())));
        twoDCost = nodes2D[(int)start.getY() * width + (int)start.getX()].getG() - twoDoffset;
        // FIXME: why getG() here, isn't the function update H?
    }

    start.setH(std::max(reedsSheppCost, std::max(dubinsCost, twoDCost)));

since dubins is default false, the last lambda is:

start.setG(std::max(reedsSheppCost, twoDCost));

这个地方必须是取G,因为在作者的传统A*函数中,最终的cost值是记录在G中的,所以只能用getG去拿到数据。然后和Reed曲线对比,取最大值。

why the biggest?

writer said in the comment, for admissible.

assume that A star with a smaller cost, while Reed and Sheep is the shortest path in theory when the obstacles are not taken into consideration.

So there only left with one solution, conventional A star is heavily not dynamic allowable. In this situation, a greater cost is admissible for forward planning.

Remind that updateH in Node2D gets the Euclid distance.

2.4.5 Hybrid A star process

if goal, stop and return this successor node
if within dubins search and collision free, use the dubins path
else, proceed normal search
	if successor is valid and isn't obstacle,
		if successor is not in close list and has the same index as the predecessor,
			update G and set new G
			if successor not on open list or found a shorter way to the cell
				get H, update H using updateH in algorithm.cpp
				if the successor is in the same cell but the f value is larger
					delete and continue
				else if successor is in the same cell and the C value is lower
					set predecessor to predecessor of predecessor

2.5 Non-linear optimization and Non-parametric interpolation

In this part, we’ll focus on smoother.cpp and its header.

after the hybrid a star process, now come to optimization.

in main.cpp

        // TRACE THE PATH
        smoother.tracePath(nSolution);
        // CREATE THE UPDATED PATH
        path.updatePath(smoother.getPath());
        // SMOOTH THE PATH
        smoother.smoothPath(voronoiDiagram);
        // CREATE THE UPDATED PATH
        smoothedPath.updatePath(smoother.getPath());

and in the planner.h

    /// The path produced by the hybrid A* algorithm
    Path path;
    /// The smoother used for optimizing the path
    Smoother smoother;
    /// The path smoothed and ready for the controller
    Path smoothedPath = Path(true);
    /// The visualization used for search visualization

there are several class variables.

2.5.1 trace path

definition of this function:

    /*!
        \brief Given a node pointer the path to the root node will be traced recursively
        \param node a 3D node, usually the goal node
        \param i a parameter for counting the number of nodes
    */
    void tracePath(const Node3D* node, int i = 0, std::vector<Node3D> path = std::vector<Node3D>());

3 parameters, including one default parameter path.

void Smoother::tracePath(const Node3D* node, int i, std::vector<Node3D> path) {
    if (node == nullptr) {
        this->path = path;
        return;
    }

    i++;
    path.push_back(*node);
    tracePath(node->getPred(), i, path);
}

In planner,

smoother.tracePath(nSolution);

nSolution is a variable of Node3D* type.

干嘛非得自己调用自己,直接取length然后for循环不行吗?

2.5.2 update path

analyze the function updatePath.

3. Tricks

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Hybrid A*示例代码KTH的path_planner分析 的相关文章

  • PCB原理图绘制(种草立创eda)

    首先 xff0c 相对于我们平时所用的ad 这个就很适合我们英文不好的中国人了 然后这个一站式搞定 xff0c 画完商城下单就可以做我们的板子了 整个设计界面也很友好 xff0c 封装库也不需要我们自己封装 很多商城里面都有 xff0c 可
  • 操作系统学习-练习题个人总结(三)

    操作系统学习 练习题个人总结 xff08 三 xff09 第二章 操作系统硬件基础 一 第二章 中断和特权级 课前测试 1 错题解析 从用户态到内核态的转换是由 xff08 中断硬件 xff09 完成的 解析 xff1a 扩展资料 xff1
  • c++转换python返回的字符串

    PyArg Parse可以将python返回参数转换为c 43 43 类型 对于字符串转换用 xff0c 如下方法 xff1a xff08 格式必须这样 xff0c 其他方式都转换不了 xff09 char p 61 NULL PyArg
  • Esp8266天猫精灵_RGB灯_非点灯平台

    arduino接入阿里云 天猫精灵 云智能APP RGB灯 鉴于很多平台的物联网设备数量都受到限制 xff0c 比如说blinker xff0c 免费的只有5个 xff0c 使用了物联网里的老大哥阿里云 xff0c 性能稳定 xff0c 生
  • C++day09 类域(全局作用域、类作用域、块作用域)和深拷贝、写时复制、短字符串优化、第三方库优化短字符

    文章目录 1 总体目录框架2 类域 xff08 1 xff09 全局作用域 xff08 2 xff09 类作用域 xff08 3 xff09 块作用域 xff08 4 xff09 具体代码 3 std string底层实现 1 总体目录框架
  • 一天入门TM4C123GH6PM(从STM32进行比较学习)

    从STM32到TM4C123 主要内容 xff1a 一 系统时钟 二 GPIO相关 三 通用定时器相关 四 PWM相关 五 UART通信相关 写在前面 xff1a 进入TI的学习 xff0c 说明STM32 已经掌握的差不多了 xff0c
  • ffmpeg 报错Encoder (codec h264) not found for output stream #0:0

    使用ffmpeg 制作流媒体的视频文件 同样的命令在本地的windows环境是正常的 xff0c 在linux 上就不行了 报错了 根据最后一行的提示 xff0c Encoder codec h264 not found for outpu
  • Datax定时增量读取MongoDB到本地配置文件

    Datax定时增量读取MongoDB到本地配置文件 功能 1 gt DataX实现读取MongDB 2 gt 按照时间增量读取 3 gt 定时执行 xff08 使用调度工具自行实现 xff09 代码 span class token pun
  • 我们为什么需要自动化运维?

    随着企业服务器和交换机数量越来越多 xff0c 当到达几百台 xff0c 上千台服务器和交换机之后 xff0c 服务器和交换机日常管理也逐渐繁杂 xff0c 每天如果通过人工去频繁的更新或者部署及管理这些服务器和交换机 xff0c 势必会浪
  • C语言字符数组与字符串的使用及加结束符'\0'的问题

    1 字符数组的定义与初始化 字符数组的初始化 xff0c 最容易理解的方式就是逐个字符赋给数组中各元素 char str 10 61 I a m h a p p y 即把10个字符分别赋给str 0 到str 9 10个元素 如果花括号中提
  • Ubuntu之桌面安装及启动级别切换

    一 需求说明 某开发测试环境操作系统为Ubuntu20 04 给开发人员安装了xrdp 一次远程桌面连接过程中异常奔溃后无法再次远程连接 重启xrdp服务后所有人连接远程连接均出现闪退 为了进一步排查和测试需要搭建一个xrdp测试环境 当前
  • 如何将windows中的文件上传到虚拟机中?

    今天在linux系统中装了mysql xff0c 本来是用wget命令在官网下载的 xff0c 后来实在是慢 等了几分钟实在看不下去每秒十几k的下载速度 xff0c 于是将这个压缩包 xff08 tar xz结尾 xff09 下载到了win
  • 使用SQLyog连接Linux(CentOS版本)下的MySQL8数据库报2003以及1045错误的解决方法

    今天想尝试一下mysql的图形化管理工具 xff0c 于是下载了SQLyog xff0c 连接时却遇到了以下错误 xff1a 其中192 168 0 10是我linux下设置的inet xff0c 我们是通过它远程连接数据库 xff0c 这
  • servlet 学习笔记3

    1 会话 a 定义 xff1a 一个浏览器与一个服务端的一次完整的交流 b 特点 xff1a 在一次会话过程中 xff0c 经历多次请求与响应 在一次会话过程中 xff0c 同一个浏览器往往访问多个Servlet c 需求 xff1a 在一
  • JDBC 学习笔记2

    1 处理查询结果集 xff08 遍历结果集 xff09 span class token keyword package span test span class token punctuation span span class toke
  • JDBC 学习笔记3

    1 对比Statement与PreparedStatement Statement存在sql注入问题 xff0c PreparedStatement解决了sql注入问题 Statement是编译一次执行一次 xff0c PreparedSt
  • memset函数使用方法

    memset 函数及其作用 memset 函数原型是extern void memset void buffer int c int count buffer xff1a 为指针或是数组 c xff1a 是赋给buffer的值 count
  • 补码和原码的转化过程

    在计算机系统中 xff0c 数值一律用补码来表示 xff08 存储 xff09 主要原因 xff1a 使用补码 xff0c 可以将符号位和其它位统一处理 xff1b 同时 xff0c 减法也可按加法来处理 另外 xff0c 两个用补 码表示
  • 在ubuntu系统下安装缺少的字体(一般缺少中文字体)

    在ubuntu系统下安装缺少的字体 cite Ubuntu LaTeX 环境配置 https www cnblogs com xqmeng p 13931222 html 第一步 xff1a 下载缺少的字体 xff08 这里保证下载字体的名
  • 【数学知识】质数与质因子

    一 质数 1 概念 质数又称素数 一个大于1的自然数 xff0c 除了1和它自身外 xff0c 不能被其他自然数整除的数叫做质数 xff0c 否则称为合数 规定1既不是质数也不是合数质数的个数是无穷的 2 例题 xff1a AcWing 3

随机推荐